Aaron
Aaron

Reputation: 115

Using event handlers inside an akka.net Actor safely

I'm trying to build a file download actor, using Akka.net. It should send messages on download completion but also report download progress.

In .NET there are classes supporting asynchronous operations using more than one event. For example WebClient.DownloadFileAsync has two events: DownloadProgressChanged and DownloadFileCompleted.

Preferably, one would use the task based async version and use the .PipeTo extension method. But, I can't see how that would work with an async method exposing two events. As is the case with WebClient.DownloadFileAsync. Even with WebClient.DownloadFileTaskAsync you still need to handle DownloadProgressChanged using an event handler.

The only way I found to use this was to hook up two event handlers upon creation of my actor. Then in the handlers, I messages to Self and the Sender. For this, 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?

Currently, my solution looks like this (_client is a WebClient instance created in the constructor of the actor):

    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);
    } 

So when the download starts, some fields are set. Additionally, I make sure I Become a state where further StartDownload messages are stashed, until the DownloadCompleted message is received by Self:

    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();
        });
    }

For reference, here's the entire Actor, but I think the important stuff is in this post directly: https://gist.github.com/AaronLenoir/4ce5480ecea580d5d283c5d08e8e71b5

Upvotes: 4

Views: 1334

Answers (2)

IamDOM
IamDOM

Reputation: 278

I don't know if that is your case, but I see people treating Actors as micro services when they are simply objects. Remember Actors have internal state.

Now think about scalability, you can't scale messages to one Actor in a distributed Actor System. The messages you're sending to one Actor will be executed in the node executing that Actor.

If you want to execute download operations in parallel (for example), you do as Patrick said and create one Actor per download operation and that Actor can be executed in any available node.

Upvotes: 0

Patrick Allwood
Patrick Allwood

Reputation: 1832

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?

There's nothing inherently wrong with an actor having internal state, and members that are part of that state raising events which are handled within the actor. No more wrong than this would be if taking an OO approach.

The only real concern is if that internal state gets mixed between multiple file download requests, but I think your current code is sound.

A possibly more palatable approach may be to look at the FileDownloadActor as a single use actor, fire it up, download the file, tell the result to the sender and then kill the actor. Starting up actors is a cheap operation, and this completely sidesteps the possibility of sharing the internal state between multiple download requests.

Unless of course you specifically need to queue downloads to run sequentially as your current code does - but the queue could be managed by another actor altogether and still treat the download actors as temporary.

Upvotes: 2

Related Questions