C# 在akka.net参与者中安全地使用事件处理程序

C# 在akka.net参与者中安全地使用事件处理程序,c#,.net,events,akka.net,C#,.net,Events,Akka.net,我正在尝试使用Akka.net构建一个文件下载演员。它应该在下载完成时发送消息,但也报告下载进度 在.NET中,有一些类支持使用多个事件的异步操作。例如,WebClient.DownloadFileAsync有两个事件:DownloadProgressChanged和DownloadFileCompleted 最好使用基于任务的异步版本,并使用.PipeTo扩展方法。但是,我看不出在异步方法公开两个事件时该如何工作。与WebClient.DownloadFileAsync的情况一样。即使使用We

我正在尝试使用Akka.net构建一个文件下载演员。它应该在下载完成时发送消息,但也报告下载进度

在.NET中,有一些类支持使用多个事件的异步操作。例如,
WebClient.DownloadFileAsync
有两个事件:
DownloadProgressChanged
DownloadFileCompleted

最好使用基于任务的异步版本,并使用
.PipeTo
扩展方法。但是,我看不出在异步方法公开两个事件时该如何工作。与
WebClient.DownloadFileAsync
的情况一样。即使使用
WebClient.DownloadFileTaskAsync
,您仍然需要使用事件处理程序处理
DownloadProgressChanged

我发现使用它的唯一方法是在创建actor时连接两个事件处理程序。然后在处理程序中,我向Self和发送者发送消息。为此,我必须从事件处理程序内部引用参与者的一些私有字段。我觉得这不对,但我看不到另一条出路

在一个参与者中使用多个事件处理程序是否有更安全的方法

目前,我的解决方案如下所示(\u客户端是在参与者的构造函数中创建的
WebClient
实例):

因此,当下载开始时,会设置一些字段。此外,我要确保我
变成
一种状态,在此状态下,将进一步
StartDownload
消息隐藏起来,直到自己收到
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();
        });
    }
public void Ready()
{
接收(消息=>{
HandleStartDownload(消息);
成为(下载);
});
}
公开下载
{
接收(消息=>{
藏起来;
});
接收(消息=>{
准备好;
藏起来;
});
}
作为参考,这里是整个演员,但我认为重要的东西直接在这篇文章中:

我必须从事件内部引用演员的一些私人字段 处理程序。我觉得这不对,但我看不到另一条出路

在一个参与者中使用多个事件处理程序是否有更安全的方法

参与者具有内部状态,以及作为该状态的一部分的成员在参与者内部处理引发事件,这本身并没有什么错。如果采用面向对象的方法,也不会比这更错误

唯一真正关心的是,如果内部状态在多个文件下载请求之间混合,但我认为您当前的代码是可靠的

一种可能更容易接受的方法是将
FileDownloadActor
视为一个单一使用的参与者,启动它,下载文件,将结果告诉发送者,然后杀死该参与者。启动actors是一种廉价的操作,这完全避免了在多个下载请求之间共享内部状态的可能性


当然,除非您特别需要将下载排队,以便像当前代码那样按顺序运行-但是队列可以完全由另一个参与者管理,并且仍然将下载参与者视为临时参与者。

我不知道这是否是您的情况,但我看到人们将参与者视为微服务,而他们只是对象。记住演员有内在状态

现在考虑一下可伸缩性,在分布式参与者系统中,您不能将消息扩展到一个参与者。发送给某个参与者的消息将在执行该参与者的节点中执行


如果您想并行执行下载操作(例如),请按照Patrick所说的那样,为每个下载操作创建一个参与者,并且该参与者可以在任何可用节点中执行。

谢谢,我确实会采用一次性参与者的方法。我会做一些其他的东西来排队,因为我希望每个主机都有一个下载队列。
    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();
        });
    }