Multithreading GenServer上的Elixir非阻塞线程?
我试图完成一项简单的任务,但遇到了巨大的困难 请假设我有一个Multithreading GenServer上的Elixir非阻塞线程?,multithreading,elixir,nonblocking,gen-server,Multithreading,Elixir,Nonblocking,Gen Server,我试图完成一项简单的任务,但遇到了巨大的困难 请假设我有一个GenServer,其中一个回调如下: @impl true def handle_call(:state, _, state) do # Something that would require 10 seconds newState = do_job() {:reply, newState, newState} end 如果我是对的,从客户端调用GenServer.call(:server,:s
GenServer
,其中一个回调如下:
@impl true
def handle_call(:state, _, state) do
# Something that would require 10 seconds
newState = do_job()
{:reply, newState, newState}
end
如果我是对的,从客户端调用GenServer.call(:server,:state)
将阻塞服务器10秒钟,然后新状态将返回给客户端
好的。我希望服务器在不被阻止的情况下处理此任务。我尝试过使用Tasks,但是Task.wait/2
和Task.yield/2
阻止服务器
我希望服务器不要阻塞,在这10秒钟之后,在客户端接收结果。这怎么可能呢
如果我是对的,从客户端调用GenServer.call(:server,:state)
服务器端会阻塞服务器10秒钟,然后进入新状态
将返回给客户
对。长生不老药会按照你的要求来做,在这句话中:
newState = do_job()
您正在告诉elixir将do_job()
的返回值分配给变量newState
。elixir执行该赋值的唯一方法是获取go\u job()
…的返回值,这将花费10秒
我希望服务器不要阻塞,在这10秒之后,接收
客户端上的结果
一种方法是让GenServerspawn()
一个新进程执行10秒的函数,并将客户机的pid传递给新进程。当新进程从10秒函数获取返回值时,新进程可以使用客户端pid向客户端发送消息
这意味着客户端需要调用handle\u call()
而不是handle\u cast()
,因为服务器的handle\u cast()
实现没有包含客户端pid的from参数变量。另一方面,handle\u call()。请注意,spawn()
会立即返回,这意味着handle\u call()
可以立即返回一个回复,如:working\u on\u it
下一个问题是:客户端如何知道GenServer生成的新进程何时完成了10秒函数的执行?客户端无法知道服务器上的某个无关进程何时完成执行,因此客户端需要在接收中等待,直到消息从生成的进程到达。而且,如果客户机正在检查其邮箱中的邮件,那么了解发件人将很有帮助,这意味着handle\u call()
还应该将生成的进程的pid返回给客户机。客户机的另一个选择是,每隔一段时间在执行其他工作之间轮询其邮箱。为此,客户端可以在中定义一个具有短超时的接收,然后调用after子句中的函数来执行一些客户端工作,然后递归调用包含接收的函数,以便该函数再次检查邮箱
现在如何处理任务
?根据报告:
如果您正在使用异步任务,则必须等待答复
那么,如果您必须等待,那么异步任务有什么好处呢?答:如果一个进程至少需要执行两个长时间运行的函数,那么该进程可以使用Task.async()
同时运行所有函数,而不是执行一个函数并等待它完成,然后执行另一个函数并等待它完成,然后再执行另一个函数等等
但是,Task还定义了一个函数:
开始(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)>
在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)>
iex(1)>{:ok,server\u pid}=GenServer.start\u链接(Gen1.server,[])
{:好的,#PID}
iex(2)>result=GenServer.call(服务器pid,长:15\u 000)
…挂起10秒。。。
:狗
iex(3)>
我以前尝试过生成流程替代方案,但当我使用process.send将结果发送回客户端时,客户端无法在终端上获得输出,这就是我没有检查邮箱的原因!非常感谢您的详细回复,我能看到光明!我会试试任务的。斯塔我能看到光明--好。如果灯没有一直亮着,我添加了一个简单的例子。假设你做的第一个例子。如果您不希望客户端等待,您只需要在客户端创建一个进程来执行接收?但是后来。您还必须等待流程响应。。。嗯,也许这是不可能的?@deko,在某个时候,客户端必须输入一个receive来读取消息。客户端在什么时间点进入接收,以及客户端在接收中等待多长时间取决于您。另一方面,在Phoenix服务器上,有一些称为频道的东西,它们使用javascript,允许服务器在数据可用时将数据注入网页,Phoenix有一个新功能,名为LiveView
,它在不使用javascript的情况下也能做同样的事情。
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)>