GenServer 及其状态

GenServer and its state

我这个简单的 genserver:

def handle_info(:tick, items) do
  items2 = do_job(items)
  tick()
  {:noreply, items2}
end

在 "do_job" 中,我需要 a) 遍历项目,b) 发出一个可能需要很长时间的 http 请求,c) 根据响应,更新数据库并通过删除当前项目来完成它来自 "items" 或仅更新数据库:

def do_job(items) do
  Enum.each(items, fn(a) -> # or Enum.map
    Task.start fn ->

      case external_request(a) do
        {:terminate, data} ->
          Repo.update(....)
          remove(a) # send a message to this GenServer to remove itself

        {:continue, data} ->
          Repo.update(....)
      end

    end
  end)
end

1) 我是否 必须 return 一个新值 -- 更新 list/state -- 从 "do_job" 允许我的 GenServer 工作合适吗?

2) 如果可以,怎么做?我无法从 "do_job" return 更新状态,因为我为每个项目创建了一个任务,因为它需要在数据库中发送 http 请求和 CRUD 操作。这就是异步任务的原因。

3) 一般来说, GenServer 自己管理状态变量,在本例中是 "items"。是什么让 GenServer 了解如何更新它?让我们考虑一下这段代码:

def add(a) do
  GenServer.cast(__MODULE__, {:add, a}) # what/who utilizes this return value?
end

def remove(....) do
  # ....



def handle_cast({:add, a}, items) do
  {:noreply, [a | items]}  # what/who utilizes this return value?
end

客户端不使用来自"add"或"remove"的returned值,因此它被丢弃了。尽管如此,当客户端调用 "add" 3 次时,此 GenServer 将在列表中包含 3 个项目。但为什么? GenServer 如何处理?

1) Do I have to return a new value -- updated list/state -- from "do_job" to allow my GenServer work properly?

没有到return来自handle_info的新更新列表,但如果您return相同的列表,下一条 :tick 消息可能会导致对相同的项目发出一组新的请求,即使他们的第一个 运行 最终会 return {:terminate, _} 如果您通过发送来处理删除作业do_job 稍后发给此 GenServer 的消息。

2) If so, how? I can't return an updated state from "do_job" because I create a Task for each item because it requires sending an http request and CRUD operation in a database. That's why an asyncronous task.

您可以 return 新列表,同时 运行 并行处理作业。这可以通过首先生成所有任务,然后等待所有任务来完成。我肯定会建议这种方式,因为它简单有效地防止重复工作 运行.

这是此方法的一个实现(未经测试):

def do_job(items) do
  items
  |> Enum.map(fn(a) ->
    Task.async(fn ->
      case external_request(a) do
        {:terminate, data} ->
          Repo.update(....)
          [] # remove this item from list
        {:continue, data} ->
          Repo.update(....)
          [a] # keep this item in list
      end
    end)
  end)
  |> Enum.flat_map(&Task.await/1)
end

do_job 现在将获取项目列表,将所有项目并行传递给 external_request,然后等到所有项目都已 returned,然后删除所有项目来自 {:terminate, _} 案例的列表。

您可以保持 handle_info 实现与以前相同。