Способ сделать это, чтобы сохранить прочь ссылку на отправителя в handle_call
и вызвать gen_server:reply
на более позднем этапе. То есть, в то время как вы обычно написать handle_call
функцию:
handle_call(foo, _From, State) ->
{reply, {ok, bar}, State}.
вы могли бы сделать что-то вроде этого:
handle_call(foo, From, State = #state{pending = Pending}) ->
NewState = State#state{pending = [From | Pending]},
{noreply, NewState}.
А потом, возможно, в handle_info
:
handle_info({bar_event, Data}, State = #state{pending = [Head | Tail]) ->
gen_server:reply(Head, {ok, Data}),
NewState = State#state{pending = Tail},
{noreply, NewState}.
Это означает, что когда процесс вызывает gen_server:call(Pid, foo)
, он будет блокироваться до тех пор, пока серверный процесс не получит {bar_event, Data}
, после чего сервер вернет данные, содержащиеся в событии, вызывающему абоненту.
Я попытался упростить пример, сохранив информацию о вызывающих абонентах в «стеке», поэтому в случае нескольких одновременных абонентов последний вызывающий абонент вернется первым и наоборот. Во многих случаях вам нужно сохранить информацию о вызывающем абоненте с помощью какого-либо ключа, который вы можете посмотреть, когда получите асинхронное событие.
Это также может быть сделано в процессе gen_fsm; значения вызовов функций и возврата аналогичны.
Спасибо! Это именно то, что мне нужно. Я полностью забыл о {noreply, ...} "return". – peitur
Будьте осторожны, так как «gen_server: call» имеет встроенный тайм-аут в 5 секунд, после которого автоматически генерируется ошибка. Длительность таймаута может быть задана с помощью третьего аргумента: миллисекунды или бесконечности. – rvirding
Yup, я столкнулся с этим вопросом в предыдущем приложении. Теперь у меня обычно есть либо бесконечность, либо я передаю значение тайм-аута (которое предназначено для обратного вызова). Это делает вещи немного «грязными», хотя :( – peitur