Reputation: 349
I am trying to set up DI followed by official akka.net document (http://getakka.net/docs/Dependency%20injection#autofac). However Actors never create. What's wrong in the following my code?
public class Worker: ReceiveActor
{
public Worker()
{
Receive<string>(m => Console.WriteLine("Worker is working"));
}
}
public class WorkerManager : ReceiveActor
{
public WorkerManager()
{
Receive<string>(m => Console.WriteLine("Manager start supervise"));
}
protected override void PreStart()
{
Context.ActorOf(Context.DI().Props<Worker>(), "Worker1");
}
}
class Program
{
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<Worker>();
builder.RegisterType<WorkerManager>();
var system = ActorSystem.Create("DiTestSystem");
IContainer container = builder.Build();
IDependencyResolver resolver = new AutoFacDependencyResolver(container, system);
var manageRef = system.ActorOf(system.DI().Props<WorkerManager>(), "Manager1");
manageRef.Tell("Hello");
system.ActorSelection("/user/Manager1/Worker1").Tell("Hello");
Console.ReadLine();
}
}
When run the code, I got this
[INFO][24/04/2017 1:50:11 AM][Thread 0006][akka://DiTestSystem/user/Manager1/Worker1] Message String from akka://DiTestSystem/deadLetters to akka://DiTestSystem/user/Manager1/Worker1 was not delivered. 1 dead letters encountered. Manager start supervise
Upvotes: 0
Views: 850
Reputation: 3831
The problem here is a race condition. This could happen regardless of whether or not you are creating your actors using a DI container.
When working with an actor system you need to remember that everything happens asynchronously. You code is allowed to continue after sending the message to the WorkerManager
actor but that does not mean that message has actually been received and processed. It is actually posted to a mailbox for the actor to process on a different thread when it is ready.
It would also be best to not attempt to access the Worker
actor directly given that you have a WorkerManager
actor in place. Just like in real life, you would normally ask a manager to organise for some work to be done, and the manager would then in turn decide what workers are required, and assign the necessary work to them.
Akka.NET has a router feature that is useful for such scenarios. I have updated your sample code with some additional logging and the router configuration.
void Main()
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<Worker>();
builder.RegisterType<WorkerManager>();
var container = builder.Build();
var system = ActorSystem.Create("DITestSystem");
var resolver = new AutoFacDependencyResolver(container, system);
var manager = system.ActorOf(system.DI().Props<WorkerManager>(), "Manager");
Console.WriteLine("Program: Created Manager");
for (int i = 0; i < 10; i++)
{
manager.Tell("Hello");
}
Console.ReadKey(true);
}
public class Worker : ReceiveActor
{
public Worker()
{
Receive<string>(m => Console.WriteLine($"Worker {Context.Self.Path.Name} received: {m}"));
}
protected override void PreStart()
{
Console.WriteLine($"PreStart: {Context.Self.Path}");
}
}
public class WorkerManager : ReceiveActor
{
IActorRef worker;
public WorkerManager()
{
Receive<string>(m =>
{
Console.WriteLine($"Manager received: {m}");
worker.Tell(m);
});
}
protected override void PreStart()
{
Console.WriteLine($"PreStart: {Context.Self.Path}");
var props = Context.DI().Props<Worker>().WithRouter(new RoundRobinPool(5));
worker = Context.ActorOf(props, "Worker");
}
}
Note this line in the WorkerManager
actor that configures a router for the Worker
actors.
var props = Context.DI().Props<Worker>().WithRouter(new RoundRobinPool(5));
This will cause the ManagerActor
to forward messages to 5 child Worker
actors in a round-robin manner. The message routing is taken care of for you so there is no need to interact directly with the Worker
actors.
The sample now also fires off 10 messages to the ManagerActor
immediately after getting a reference to it.
for (int i = 0; i < 10; i++)
{
manageRef.Tell("Hello");
}
With more messages in play and some logging you can see that the order of things is not deterministic. This is the output from one run.
Program: Created Manager
PreStart: akka://DITestSystem/user/Manager
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
PreStart: akka://DITestSystem/user/Manager/Worker/$b
PreStart: akka://DITestSystem/user/Manager/Worker/$d
PreStart: akka://DITestSystem/user/Manager/Worker/$f
Worker $b received: Hello
Worker $b received: Hello
Worker $f received: Hello
Worker $f received: Hello
PreStart: akka://DITestSystem/user/Manager/Worker/$c
Worker $c received: Hello
Worker $c received: Hello
PreStart: akka://DITestSystem/user/Manager/Worker/$e
Worker $d received: Hello
Worker $d received: Hello
Worker $e received: Hello
Worker $e received: Hello
And this is the output from another.
Program: Created Manager
PreStart: akka://DITestSystem/user/Manager
PreStart: akka://DITestSystem/user/Manager/Worker/$b
PreStart: akka://DITestSystem/user/Manager/Worker/$c
PreStart: akka://DITestSystem/user/Manager/Worker/$d
PreStart: akka://DITestSystem/user/Manager/Worker/$f
PreStart: akka://DITestSystem/user/Manager/Worker/$e
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Worker $d received: Hello
Worker $e received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Manager received: Hello
Worker $f received: Hello
Worker $e received: Hello
Manager received: Hello
Worker $f received: Hello
Worker $b received: Hello
Worker $b received: Hello
Worker $c received: Hello
Worker $c received: Hello
Worker $d received: Hello
You can see that in the first run the WorkerManager
had received all messages before creating any child Worker
actors. In the second run it had created all the child Worker
actors before receiving any of the messages.
The main point to remember is that it is best to communicate with messages and not make assumptions about when things happen.
Upvotes: 1
Reputation: 349
What I found is Actor cannot be created fast enough with Autofac.
When I added Thread.Sleep(20), finally, create actor properly.
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<WorkerManager>();
builder.RegisterType<Worker>();
var system = ActorSystem.Create("DiTestSystem");
IContainer container = builder.Build();
IDependencyResolver resolver = new AutoFacDependencyResolver(container, system);
var manageRef = system.ActorOf(system.DI().Props<WorkerManager>(), "Manager1");
Thread.Sleep(20); // ADDED THIS LINE
manageRef.Tell("Hello");
system.ActorSelection("/user/Manager1/Worker1").Tell("Hello");
Console.ReadLine();
}
I think it is quite big problem, because this approach cannot guarantee actor always create/resolve during sleep time.
I keep open this problem, because I think it cannot be a proper answer.
Upvotes: 1