Если я прав, вызов GenServer.call (: server,: state) со стороны клиента блокирует сервер на 10 секунд, а затем новое состояние возвращается клиенту.
Да.Elixir делает то, что вы говорите, и в этой строке:
newState = do_job()
вы говорите elixir назначить возвращаемое значение do_job()
переменной newState
.Единственный способ, которым elixir может выполнить это назначение, - получить возвращаемое значение go_job()
...., что займет 10 секунд.
Я хочу, чтобы сервер не блокировался, и после этих 10 секундполучить результат на клиентском терминале.
Один из подходов заключается в том, чтобы GenServer spawn()
запустил новый процесс, чтобы выполнить 10-секундную функцию и передать pid клиента новому процессу.Когда новый процесс получает возвращаемое значение из 10-секундной функции, новый процесс может send()
сообщение клиенту, используя клиентский pid.
Это означает, что клиент должен будет вызвать handle_call()
, а неhandle_cast()
, поскольку серверная реализация handle_cast()
не имеет переменной параметра from
, содержащей клиентский pid.С другой стороны, handle_call()
действительно получает клиентский pid в переменной параметра from
, поэтому сервер может передать клиентский pid порожденному процессу.Обратите внимание, что spawn()
возвращается немедленно, что означает, что handle_call()
может немедленно вернуться с ответом, подобным :working_on_it
.
Следующая проблема: как клиент узнает, когда новый процесс, который породил GenServer, имеетзакончил выполнение функции 10 секунд?Клиент не может знать, когда какой-либо посторонний процесс на сервере завершил выполнение, поэтому клиент должен ждать в получении, пока не прибудет сообщение от порожденного процесса.И, если клиент проверяет сообщения в своем почтовом ящике, было бы полезно узнать, кто был отправителем, что означает, что handle_call()
также должен вернуть клиенту pid порожденного процесса.Другим вариантом для клиента является опрос своего почтового ящика каждый раз между приступами выполнения другой работы.Для этого клиент может определить получение с коротким тайм-аутом в после предложения , затем вызвать функцию в after clause
для выполнения некоторой клиентской работы, после чего следует рекурсивный вызов функции, содержащейполучить, чтобы функция снова проверила почтовый ящик.
А как насчет Task
?Согласно документам Task :
Если вы используете асинхронные задачи, вы должны ждать ответа ...
Ну, тогда что хорошего в асинхронном задании, если нужно подождать?Ответ: если у процесса есть хотя бы двух долго выполняющихся функций, которые ему необходимо выполнить, то процесс может использовать Task.async()
для одновременного запуска всех функций, вместо того, чтобы выполнять одну функцию и ждать, пока она не будетзавершается, затем выполняет другую функцию и ждет, пока она не завершится, затем выполняет другую и т. д.
Но Task также определяет функцию start () :
start (mod, fun, args)
Запускает задание.
Используется только в том случае, если задание используется для побочных эффектов (т.е. не интересуетвозвращаемый результат), и он не должен быть связан с текущим процессом.
Звучит так, будто Task.start()
завершает то, что я описал в первом подходе.Вам необходимо определить fun
, чтобы он выполнял 10-секундную функцию, а затем отправить сообщение обратно клиенту после завершения 10-секундной функции (= побочный эффект ).
Ниже приведен простой пример GenServer, который порождает долго выполняющуюся функцию, которая позволяет серверу оставаться отзывчивым на запросы других клиентов, пока выполняется долго работающая функция:
a.exs:
defmodule Gen1.Server do
use GenServer
@impl true
def init(init_state) do
{:ok, init_state}
end
def long_func({pid, _ref}) do
Process.sleep 10_000
result = :dog
send(pid, {self(), result})
end
@impl true
def handle_call(:go_long, from, state) do
long_pid = spawn(Gen1.Server, :long_func, [from])
{:reply, long_pid, state}
end
def handle_call(:other, _from, state) do
{:reply, :other_stuff, state}
end
end
Сессией iex будет клиент:
~/elixir_programs$ iex a.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> {:ok, server_pid} = GenServer.start_link(Gen1.Server, [])
{:ok, #PID<0.93.0>}
iex(2)> long_pid = GenServer.call(server_pid, :go_long, 15_000)
#PID<0.100.0>
iex(3)> GenServer.call(server_pid, :other)
:other_stuff
iex(4)> receive do
...(4)> {^long_pid, reply} -> reply
...(4)> end
:dog
iex(7)>
Переменная типа long_pid
будет соответствовать чему угодно.Чтобы long_pid
соответствовал только его текущему значению, вы указываете ^long_pid
(^
называется оператором вывода).
GenServer также позволяет блокировать вызов клиента на handle_call()
, в то же время позволяя серверу продолжить выполнение.Это полезно, если клиент не может продолжать работу до тех пор, пока не получит некоторые необходимые данные с сервера, но вы хотите, чтобы сервер оставался отзывчивым на других клиентов.Вот пример этого:
defmodule Gen1.Server do
use GenServer
@impl true
def init(init_state) do
{:ok, init_state}
end
@impl true
def handle_call(:go_long, from, state) do
spawn(Gen1.Server, :long_func, [from])
{:noreply, state} #The server doesn't send anything to the client,
#so the client's call of handle_call() blocks until
#somebody calls GenServer.reply().
end
def long_func(from) do
Process.sleep 10_000
result = :dog
GenServer.reply(from, result)
end
end
В iex:
iex(1)> {:ok, server_pid} = GenServer.start_link(Gen1.Server, [])
{:ok, #PID<0.93.0>}
iex(2)> result = GenServer.call(server_pid, :go_long, 15_000)
...hangs for 10 seconds...
:dog
iex(3)>