這裏的問題是一個競爭條件。無論您是否使用DI容器創建演員,都可能發生這種情況。
當使用actor系統時,你需要記住所有事情都是異步發生的。在將消息發送給WorkerManager
參與者之後,您的代碼被允許繼續,但這並不意味着該消息實際上已被接收和處理。它實際上被髮布到一個郵箱中,以便演員在準備就緒時在另一個線程上處理它。
最好不要試圖直接訪問Worker
演員,因爲您已經有WorkerManager
演員。就像在現實生活中一樣,你通常會要求經理組織一些工作,然後經理會依次決定需要什麼樣的工作人員,併爲他們分配必要的工作。
Akka.NET有一個router feature對於這樣的場景很有用。我已經用一些額外的日誌記錄和路由器配置更新了您的示例代碼。
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");
}
}
注意這條線在WorkerManager
演員是配置爲Worker
演員的路由器。
var props = Context.DI().Props<Worker>().WithRouter(new RoundRobinPool(5));
這將導致ManagerActor
在一個循環的方式轉發至5子Worker
演員。消息路由被照顧好了,因此不需要直接與Worker
角色進行交互。
該樣本現在還會在收到引用後立即向ManagerActor
發送10條消息。
for (int i = 0; i < 10; i++)
{
manageRef.Tell("Hello");
}
隨着更多消息的播放和一些日誌記錄,您可以看到事物的順序不是確定性的。這是一次運行的輸出。
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
這是另一個輸出。
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
你可以看到,在第一次運行時WorkerManager
產生任何孩子Worker
演員之前收到的所有郵件。在第二次運行中,它在接收到任何消息之前創建了所有的孩子Worker
演員。
要記住的要點是,最好與消息進行交流,而不是對事情發生的時間做出假設。
謝謝你的解釋。 DI容器在程序向演員發送消息之前不會創建演員,是否正確? – ShootingStar
當ActorOf被調用時,actor系統將開始創建actor的實例。這將發生在另一個線程上。只要您擁有'IActorRef',您就可以開始發送消息。這些可以傳遞給演員的郵箱,甚至可以在演員完全在另一個線程上創建之前。 –