Ответ Адама великолепен, но у меня есть одно замечание, чтобы добавить
Использование handle_call заблокирует процесс на время вызова.
Это всегда верно для клиента, который сделал вызов handle_call . Мне потребовалось некоторое время, чтобы обернуть голову, но это не обязательно означает, что gen_server также должен блокироваться при ответе на handle_call.
В моем случае я столкнулся с этим, когда создал базу данных, обрабатывающую gen_server, и сознательно написал запрос, который выполнил SELECT pg_sleep(10)
, который на PostgreSQL говорит «спит в течение 10 секунд», и был моим способом тестирования очень дорогих запросы. Моя задача: я не хочу, чтобы база данных gen_server сидела и ждала завершения работы базы данных!
Мое решение состояло в том, чтобы использовать gen_server: reply / 2 :
Эта функция может использоваться gen_server для явной отправки ответа клиенту, который вызвал call / 2,3 или multi_call / 2,3,4, когда ответ не может быть определен в возвращаемом значении Module: handle_call / 3.
В коде:
-module(database_server).
-behaviour(gen_server).
-define(DB_TIMEOUT, 30000).
<snip>
get_very_expensive_document(DocumentId) ->
gen_server:call(?MODULE, {get_very_expensive_document, DocumentId}, ?DB_TIMEOUT).
<snip>
handle_call({get_very_expensive_document, DocumentId}, From, State) ->
%% Spawn a new process to perform the query. Give it From,
%% which is the PID of the caller.
proc_lib:spawn_link(?MODULE, query_get_very_expensive_document, [From, DocumentId]),
%% This gen_server process couldn't care less about the query
%% any more! It's up to the spawned process now.
{noreply, State};
<snip>
query_get_very_expensive_document(From, DocumentId) ->
%% Reference: http://www.erlang.org/doc/man/proc_lib.html#init_ack-1
proc_lib:init_ack(ok),
Result = query(pgsql_pool, "SELECT pg_sleep(10);", []),
gen_server:reply(From, {return_query, ok, Result}).