Erlang 异步消息处理与 gen_server:cast/2

发布于 2024-11-15 07:39:59 字数 207 浏览 2 评论 0原文

我正在寻找 Erlang 使用 gen_server:cast/2 进行异步消息处理的好例子。

我在 OTP ssh 模块中看到了一个示例,它通过以下方式接收请求 module:handle_cast/2,保存在模块的本地队列中,稍后通过显式向调用者发送消息的方式发回与请求对应的回复消息。当我尝试阅读它时,我几乎无法跟踪代码,也无法理解其思想。

一段伪代码值得赞赏。

I'm looking for good examples of Erlang asynchronous message handling with gen_server:cast/2.

I've seen an example in the OTP ssh module, which receives a request through
Module:handle_cast/2, holding it in a local queue of the module, and sending back a reply message corresponding to the request later, by explicitly sending a message to the caller. When I tried to read it, I could barely keep track of the code, and wasn't able to grasp the idea.

A piece of pseudo code is appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

浅唱々樱花落 2024-11-22 07:39:59

我相信您指的是 ssh_connection_manager 模块。

当您执行 gen_server:cast/2 时,请求将在 Module:handle_cast/2 函数中处理。这里需要注意一些事情:

  • handle_cast 参数中,您没有有关发送者的信息,因此您无法 - 除非您在消息本身中发送此信息 - 向其发送回一些结果。
  • 客户端发出gen_server:cast/2后,不会等待回复。实际上,它甚至不关心消息是否到达(有一些例外)。
  • handle_cast/2 中,您只能返回一个 noreplystop 元组,因此无法在那里返回回复。

也就是说,您一直在查看的代码背后的想法应该是(简化事情):

  • 创建了一个初始的同步 gen_server:call/2
  • 客户端的 From 被传递到服务器并保存到其状态中(实际上,似乎使用该参数创建了一个额外的进程)。当您无法在handle_call 时计算返回值时,这个技巧非常有用。
  • 此时,您有两种可能性,具体取决于您是否需要更多信息来计算来自其他客户端 (A) 还是来自同一客户端 (B) 的结果:

    • A.在handle_call中返回类似{noreply NewState}的内容。这将使您能够在客户端仍在等待时处理其他请求。当结果准备好后,您可以使用 gen_server:reply() 将其发送回客户端。
    • B.返回 {reply, ok, State} 给客户端。客户端将继续执行,可能会执行一系列cast/2。然后,客户端将使用新的 gen_server:call/2 询问您最终结果。

I believe you're referring to the ssh_connection_manager module.

When you perform a gen_server:cast/2, the request is handled in the Module:handle_cast/2 function. A couple of things to notice here:

  • In the handle_cast parameters you don't have information about the sender, so you cannot - unless you send this information within the message itself - send back some result to it.
  • The client, after issuing a gen_server:cast/2, won't wait for a reply. Actually, it doesn't even care if the message arrived or not (with some exceptions).
  • In an handle_cast/2, you can just return a noreply or a stop tuple, so no way to return a reply there.

Said that, the idea behind the code you've been looking at should be (simplifying things):

  • An initial synchronous gen_server:call/2 is made
  • The client's From is passed to the server and saved into its state (in reality, an extra process seems to be created with that argument). This trick is useful when you are not able to compute the return value at the time of the handle_call.
  • At this point you have to two possibilities, depending if you need more information to compute your result from other clients (A) or from the same client (B):

    • A. Return something like {noreply NewState} in the handle_call. This will give you the possibility to handle other requests while the client is still holding. When the result is ready you can send it back to the client using a gen_server:reply().
    • B. Return {reply, ok, State} to the client. The client will continue execution, maybe performing a series of cast/2. Then, the client will ask you the final result with a new gen_server:call/2.
汹涌人海 2024-11-22 07:39:59

通常,在发送强制转换时您不会期望得到直接回复,否则您将使用 gen_server:call。

一个真实的例子,我有一个 gen_server 来处理一些“通道”,并且有很多用途可以将通道名称附加到错误日志记录中。通道名称存储在 gen_server 的状态中。为了不阻止想要记录错误的进程,我不使用“获取名称”同步调用来获取前置名称,而是使用强制转换发送消息:

error(Pid, Tags) ->
    gen_server:cast(Pid, {log, error_report, Tags}).

warning(Pid, Tags) ->
    gen_server:cast(Pid, {log, warning_report, Tags}).

info(Pid, Tags) ->
    gen_server:cast(Pid, {log, info_report, Tags}).

强制转换是在一个非常简单的处理程序中处理的,即不返回。

handle_cast({log, Report, Tags}, #state{name=Name}=State) ->
    error_logger:Report([{chan, Name} | Tags]),
    {noreply, State};

如果您有异步消息要发回,则这完全独立于强制转换的处理。您需要以某种方式知道将这些消息发送到哪里,您必须以某种方式将这些消息存储在 State 中,或者您正在使用固定名称。

通常,您不应该只发送消息,而应该调用接收处理模块的函数(这可能是另一个强制转换或普通消息发送)。

Normally you don't expect a direct reply when sending a cast, otherwise you would use gen_server:call.

A real world example, I have a gen_server that handles some "channels" and there is lots of use to append the channel name to error logging. The channel name is stored in the state of the gen_server. In order not to hold up the process who wants to log an error I don't use a "get name" synchronous call to get the name an prepend but send the messages with casts:

error(Pid, Tags) ->
    gen_server:cast(Pid, {log, error_report, Tags}).

warning(Pid, Tags) ->
    gen_server:cast(Pid, {log, warning_report, Tags}).

info(Pid, Tags) ->
    gen_server:cast(Pid, {log, info_report, Tags}).

The cast is handled in a quite simple handler, that does not return.

handle_cast({log, Report, Tags}, #state{name=Name}=State) ->
    error_logger:Report([{chan, Name} | Tags]),
    {noreply, State};

If you have asynchronous messages to send back, this is totally independent of the handling of the cast. You somehow need to know where to send these messages which you have to store in the State somehow, or you are using a fixed name.

Normally you shouldn't just send a message but rather call a function of the receiving process module (which just might be another cast or plain message sending).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文