AK1005 - Warning
When accessing Sender
or Self
inside a lambda expression passed as an asynchronous method argument, you must always close over Sender
or Self
to ensure that the property is captured before the method is invoked, because there is no guarantee that asynchronous context will be preserved when the lambda is invoked inside the method.
Cause
Akka.NET tries its best to preserve asynchronous context if asynchronous codes were to run inside the actor. However, there are times where this is impossible to enforce, especially when a code that accesses variables that were thread execution specific such as the actor Context
, Sender
, and Self
is being invoked outside the actor threading context.
For example, lets assume that you're using an asynchronous third party API package that invokes a callback when it completes its operation:
using System;
using System.Threading.Tasks;
namespace ThirdPartyApi;
public sealed class JobManager
{
public async Task SubmitJobAsync(string job, Func<Task> asyncCallback)
{
// This breaks asynchronous context
await ConnectToServer().ConfigureAwait(false);
// other codes here
await asyncCallback();
}
private async Task ConnectToServer()
{
// codes here
}
}
In the code above, the ConfigureAwait(false)
call breaks the asynchronous context before the callback method were invoked. If this third party API code were to be used inside Akka.NET and Context
, Sender
, or Self
were accessed inside the callback, we will see an error during runtime:
using System;
using System.Threading.Tasks;
using Akka.Actor;
using ThirdPartyApi;
namespace MyApplication.Actors;
public sealed class MyActor : ReceiveActor
{
private readonly JobManager _jobManager = new();
public MyActor()
{
ReceiveAsync<string>(async job => {
_jobManager.SubmitJobAsync(job, async () =>
{
// This callback will not work
Context.Sender.Tell($"{job} submitted.", Self);
})
});
}
}
NotSupportedException
There is no active ActorContext, this is most likely due to use of async operations from within this actor.
at MyApplication.Actors.MyActor`1<>c__DisplayClass48_0.<<InvokeTestMethodAsync>b__1>d.MoveNext() in \src\MyApplication.Actors\MyActor.cs:line 16
Resolution
While working with a third party API, it is most likely that you will not have a way to modify their codes to make them compatible with your asynchronous flow. To avoid this entire category of problem, we should close over any access to Sontext.Self
, Context.Sender
, Self
, and Sender
property in a local variable.
For the non-working example above, we can fix it by modifying our code:
using System;
using System.Threading.Tasks;
using Akka.Actor;
using ThirdPartyApi;
namespace MyApplication.Actors;
public sealed class MyActor : ReceiveActor
{
private readonly JobManager _jobManager = new();
public MyActor()
{
ReceiveAsync<string>(async job => {
// Store Context.Sender and Self inside local variable
var sender = Context.Sender;
var self = Self;
_jobManager.SubmitJobAsync(job, async () =>
{
// Use local variables instead of accessing Context directly
sender.Tell($"{job} submitted.", self);
})
});
}
}