Почему модуль обратного вызова Erlang/OTP gen_server должен предоставлять функцию handle_cast?

Я могу понять, почему модуль обратного вызова должен предоставлять функции init и handle_call. init — для создания начального состояния, а handle_call — основная цель создания серверного процесса: обслуживание запросов.

Но я не понимаю, зачем нужен handle_cast. Не может ли модуль gen_server предоставить реализацию по умолчанию, как это делается для многих других обратных вызовов? Это может быть noop, как

handle_cast(_, State) -> {noreply, State}.

Мне кажется, что большинство модулей обратного вызова в любом случае предоставляют noops, подобные этому.


person Ilya Vassilevsky    schedule 04.05.2018    source источник
comment
Это неподходящее место, чтобы задать такой вопрос. Здесь есть 2 уровня выбора: первый о том, что поведение по умолчанию должно быть явным или неявным, команда OTP (когда они разрабатывали gen_server) явно выбрали явный путь (теперь можно определить необязательный обратный вызов, но это другое из вашего предложения); второй вариант — что должно делать приложение в случае неожиданного приведения (вы не можете запретить модулю использовать интерфейс приведения) и игнорировать это не всегда то, что вам нужно (игнорировать, регистрировать, сбой?). Может быть интересно, но это не цель этого форума.   -  person Pascal    schedule 05.05.2018
comment
Это вопрос о среде программирования. Где было бы правильно задать этот вопрос?   -  person Ilya Vassilevsky    schedule 05.05.2018


Ответы (2)


handle_cast аналогичен handle_call и используется для асинхронных вызовов gen_server, который вы используете (вызовы синхронны). Он обрабатывает запросы вместо вас, но не с ответом, как это делает call.

Подобно gen_call, он может изменить состояние вашего gen_server (или оставить его как есть, в зависимости от ваших потребностей и реализации). Также он может останавливать ваш сервер, переходить в спящий режим и т. д., как и ваши звонки — см. > для примеров и более широкого объяснения.

Как вы сказали в вопросе, это «может быть noop», но в некоторых случаях лучше реализовать и обрабатывать асинхронные вызовы на ваш сервер.

person Máté    schedule 08.05.2018
comment
Вы имеете в виду, что это просто еще одна «версия» основного обработчика, которая используется так же часто? - person Ilya Vassilevsky; 09.05.2018
comment
Да, это действительно зависит от ваших потребностей и спецификаций. Примечание. Существует также handle_info/2, который используется для обработки тайм-ауты, входящие системные сообщения. - person Máté; 09.05.2018

а handle_call — основная цель создания серверного процесса: обслуживать запросы.

Архитектура клиент-сервер может быть применена к гораздо более широкому кругу задач, чем просто веб-сервер, обслуживающий документы. Одним из примеров является частотный сервер, обсуждаемый в нескольких книгах по erlang. Клиент может запросить у сервера частоту для совершения телефонного звонка, затем клиент должен дождаться, пока сервер вернет определенную частоту, прежде чем можно будет сделать вызов. Это классическая gen_server:call() ситуация: клиент должен дождаться, пока сервер вернет частоту, прежде чем клиент сможет сделать телефонный звонок.

Однако, когда клиент заканчивает использовать частоту, клиент отправляет сообщение на сервер, говоря серверу освободить частоту. В этом случае клиенту не нужно ждать ответа от сервера, потому что клиенту все равно, какой ответ сервера. Клиенту просто нужно отправить сообщение об освобождении, после чего клиент может продолжить выполнение другого кода. Сервер отвечает за обработку сообщения об освобождении, когда у него есть время, а затем перемещает частоту из списка «занято» в список «свободно», чтобы частота была доступна для использования другими клиентами. В результате клиент использует gen_server:cast() для отправки сообщения об освобождении на сервер.

Итак, какова «основная цель» частотного сервера? Выделить или освободить частоты? Если сервер не освобождает частоты, то после определенного количества клиентских запросов частот для раздачи больше не будет, и клиенты получат сообщение о том, что «частоты недоступны». Таким образом, для правильной работы системы акт освобождения частот имеет важное значение. Другими словами, handle_call() не является «основной целью» сервера — handle_cast() одинаково важен — и оба обработчика необходимы для поддержания максимально эффективной работы системы.

Не может ли модуль gen_server предоставить реализацию по умолчанию, как это делается для многих других обратных вызовов?

Почему вы не можете сами создать шаблон gen_server, который имеет реализацию handle_cast() по умолчанию? Вот шаблон gen_server по умолчанию для emac:

-behaviour(gen_server).

%% API
-export([start_link/0]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

-define(SERVER, ?MODULE).

-record(state, {}).

%%%===================================================================
%%% API
%%%===================================================================

%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
%% @end
%%--------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%%                     {ok, State, Timeout} |
%%                     ignore |
%%                     {stop, Reason}
%% @end
%%--------------------------------------------------------------------
init([]) ->
    {ok, #state{}}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @spec handle_call(Request, From, State) ->
%%                                   {reply, Reply, State} |
%%                                   {reply, Reply, State, Timeout} |
%%                                   {noreply, State} |
%%                                   {noreply, State, Timeout} |
%%                                   {stop, Reason, Reply, State} |
%%                                   {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @spec handle_cast(Msg, State) -> {noreply, State} |
%%                                  {noreply, State, Timeout} |
%%                                  {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%%                                   {noreply, State, Timeout} |
%%                                   {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
    ok.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================
person 7stud    schedule 08.05.2018