在 akka.net Actor 中安全地使用事件处理程序
Using event handlers inside an akka.net Actor safely
我正在尝试使用 Akka.net 构建文件下载 actor。它应该在下载完成时发送消息,但也会报告下载进度。
在 .NET 中 类 支持使用多个事件的异步操作。例如 WebClient.DownloadFileAsync
有两个事件:DownloadProgressChanged
和 DownloadFileCompleted
.
最好使用基于任务的异步版本并使用 .PipeTo
扩展方法。但是,我看不出这将如何与公开两个事件的异步方法一起工作。与 WebClient.DownloadFileAsync
的情况一样。即使使用 WebClient.DownloadFileTaskAsync
,您仍然需要使用事件处理程序来处理 DownloadProgressChanged
。
我发现使用它的唯一方法是在创建演员时连接两个事件处理程序。然后在处理程序中,我向 Self 和 Sender 发送消息。为此,我必须从事件处理程序中引用参与者的一些私有字段。这对我来说感觉不对,但我看不到其他出路。
有没有更安全的方法在一个 Actor 中使用多个事件处理程序?
目前,我的解决方案如下所示(_client 是在 actor 的构造函数中创建的 WebClient
实例):
public void HandleStartDownload(StartDownload message)
{
_self = Self;
_downloadRequestor = Sender;
_uri = message.Uri;
_guid = message.Guid;
_tempPath = Path.GetTempFileName();
_client.DownloadFileAsync(_uri, _tempPath);
}
private void Client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
var completedMessage = new DownloadCompletedInternal(_guid, _tempPath);
_downloadRequestor.Tell(completedMessage);
_self.Tell(completedMessage);
}
private void Client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
var progressedMessage = new DownloadProgressed(_guid, e.ProgressPercentage);
_downloadRequestor.Tell(progressedMessage);
_self.Tell(progressedMessage);
}
所以当下载开始时,设置了一些字段。此外,我确保我 Become
隐藏更多 StartDownload
消息的状态,直到 Self:
收到 DownloadCompleted
消息
public void Ready()
{
Receive<StartDownload>(message => {
HandleStartDownload(message);
Become(Downloading);
});
}
public void Downloading()
{
Receive<StartDownload>(message => {
Stash.Stash();
});
Receive<DownloadCompleted>(message => {
Become(Ready);
Stash.UnstashAll();
});
}
供参考,这是整个 Actor,但我认为重要的东西直接在 post 中:https://gist.github.com/AaronLenoir/4ce5480ecea580d5d283c5d08e8e71b5
I must refer to some private fields of the actor from inside the event
handlers. This feels wrong to me, but I cannot see another way out.
Is there a safer way to use multiple event handlers in an Actor?
具有内部状态的 actor 以及作为该状态一部分的成员引发在 actor 内处理的事件在本质上没有错。如果采用 OO 方法,再错不过了。
唯一真正关心的是内部状态是否在多个文件下载请求之间混合,但我认为您当前的代码是正确的。
一个可能更可口的方法可能是将 FileDownloadActor
视为一个单独使用的 actor,启动它,下载文件,将结果告诉发送者,然后杀死 actor。启动 actors 是一种廉价的操作,这完全避免了在多个下载请求之间共享内部状态的可能性。
当然,除非您特别需要像当前代码那样按顺序将下载排队到 运行 - 但队列可以由另一个演员完全管理,并且仍然将下载演员视为临时。
我不知道你的情况是否如此,但我看到人们将 Actors 视为微服务,而实际上它们只是对象。请记住,Actor 具有内部状态。
现在考虑可扩展性,您无法将消息扩展到分布式 Actor 系统中的一个 Actor。您发送给一个 Actor 的消息将在执行该 Actor 的节点中执行。
如果您想并行执行下载操作(例如),您可以按照 Patrick 所说的那样为每个下载操作创建一个 Actor,并且该 Actor 可以在任何可用节点中执行。
我正在尝试使用 Akka.net 构建文件下载 actor。它应该在下载完成时发送消息,但也会报告下载进度。
在 .NET 中 类 支持使用多个事件的异步操作。例如 WebClient.DownloadFileAsync
有两个事件:DownloadProgressChanged
和 DownloadFileCompleted
.
最好使用基于任务的异步版本并使用 .PipeTo
扩展方法。但是,我看不出这将如何与公开两个事件的异步方法一起工作。与 WebClient.DownloadFileAsync
的情况一样。即使使用 WebClient.DownloadFileTaskAsync
,您仍然需要使用事件处理程序来处理 DownloadProgressChanged
。
我发现使用它的唯一方法是在创建演员时连接两个事件处理程序。然后在处理程序中,我向 Self 和 Sender 发送消息。为此,我必须从事件处理程序中引用参与者的一些私有字段。这对我来说感觉不对,但我看不到其他出路。
有没有更安全的方法在一个 Actor 中使用多个事件处理程序?
目前,我的解决方案如下所示(_client 是在 actor 的构造函数中创建的 WebClient
实例):
public void HandleStartDownload(StartDownload message)
{
_self = Self;
_downloadRequestor = Sender;
_uri = message.Uri;
_guid = message.Guid;
_tempPath = Path.GetTempFileName();
_client.DownloadFileAsync(_uri, _tempPath);
}
private void Client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
var completedMessage = new DownloadCompletedInternal(_guid, _tempPath);
_downloadRequestor.Tell(completedMessage);
_self.Tell(completedMessage);
}
private void Client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
var progressedMessage = new DownloadProgressed(_guid, e.ProgressPercentage);
_downloadRequestor.Tell(progressedMessage);
_self.Tell(progressedMessage);
}
所以当下载开始时,设置了一些字段。此外,我确保我 Become
隐藏更多 StartDownload
消息的状态,直到 Self:
DownloadCompleted
消息
public void Ready()
{
Receive<StartDownload>(message => {
HandleStartDownload(message);
Become(Downloading);
});
}
public void Downloading()
{
Receive<StartDownload>(message => {
Stash.Stash();
});
Receive<DownloadCompleted>(message => {
Become(Ready);
Stash.UnstashAll();
});
}
供参考,这是整个 Actor,但我认为重要的东西直接在 post 中:https://gist.github.com/AaronLenoir/4ce5480ecea580d5d283c5d08e8e71b5
I must refer to some private fields of the actor from inside the event handlers. This feels wrong to me, but I cannot see another way out.
Is there a safer way to use multiple event handlers in an Actor?
具有内部状态的 actor 以及作为该状态一部分的成员引发在 actor 内处理的事件在本质上没有错。如果采用 OO 方法,再错不过了。
唯一真正关心的是内部状态是否在多个文件下载请求之间混合,但我认为您当前的代码是正确的。
一个可能更可口的方法可能是将 FileDownloadActor
视为一个单独使用的 actor,启动它,下载文件,将结果告诉发送者,然后杀死 actor。启动 actors 是一种廉价的操作,这完全避免了在多个下载请求之间共享内部状态的可能性。
当然,除非您特别需要像当前代码那样按顺序将下载排队到 运行 - 但队列可以由另一个演员完全管理,并且仍然将下载演员视为临时。
我不知道你的情况是否如此,但我看到人们将 Actors 视为微服务,而实际上它们只是对象。请记住,Actor 具有内部状态。
现在考虑可扩展性,您无法将消息扩展到分布式 Actor 系统中的一个 Actor。您发送给一个 Actor 的消息将在执行该 Actor 的节点中执行。
如果您想并行执行下载操作(例如),您可以按照 Patrick 所说的那样为每个下载操作创建一个 Actor,并且该 Actor 可以在任何可用节点中执行。