Lesson 2: Actor Lifecycle Messages.
In this lesson, we will consider how to process system messages that represent different stages of the actor’s life cycle.
In this demo application, we will be gradually adding handlers for various stages of the PlaybackActor lifecycle.
But before we begin, we need to make some changes to our project.
First of all, let’s add some additional film descriptions to our project.
system.Root.Send(pid, new PlayMovieMessage("The Movie", 44));
system.Root.Send(pid, new PlayMovieMessage("The Movie 2", 54));
system.Root.Send(pid, new PlayMovieMessage("The Movie 3", 64));
system.Root.Send(pid, new PlayMovieMessage("The Movie 4", 74));
Next, let’s add a new ColorConsole class, which will allow you to change the color of the message displayed to the console without extra work.
public static class ColorConsole
{
public static void WriteLineGreen(string message)
{
var beforeColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(message);
Console.ForegroundColor = beforeColor;
}
public static void WriteLineYellow(string message)
{
var beforeColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(message);
Console.ForegroundColor = beforeColor;
}
}
And finally, we’ll edit our actor a little so that it can use the `ColorConsole () ' class, output the message content to the console in yellow. And also process different types of messages in separate methods.
public class PlaybackActor : IActor
{
public PlaybackActor() => Console.WriteLine("Creating a PlaybackActor");
public Task ReceiveAsync(IContext context)
{
switch (context.Message)
{
case PlayMovieMessage msg:
ProcessPlayMovieMessage(msg);
break;
}
return Task.CompletedTask;
}
private void ProcessPlayMovieMessage(PlayMovieMessage msg)
{
ColorConsole.WriteLineYellow($"PlayMovieMessage {msg.MovieTitle} for user {msg.UserId}");
}
}
Let’s run our application and make sure that everything works right.
Now, after all the necessary preparations, we can begin to study the system messages. And we will begin our introduction with the Started
message.
Started
The system message Started
is used to inform our code the moment when the actor starts. Let’s add the ability to process the Started
message to the ReceiveAsync ()
method.
public class PlaybackActor : IActor
{
public PlaybackActor() => Console.WriteLine("Creating a PlaybackActor");
public Task ReceiveAsync(IContext context)
{
switch (context.Message)
{
case Started msg:
ProcessStartedMessage(msg);
break;
case PlayMovieMessage msg:
ProcessPlayMovieMessage(msg);
break;
}
return Task.CompletedTask;
}
}
Now let’s implement the ProcessStartedMessage()
method in class PlaybackActor
.
private void ProcessStartedMessage(Started msg)
{
ColorConsole.WriteLineGreen("PlaybackActor Started");
}
The ProcessStartedMessage()
method will print a green message to the console, inform us that our actor has successfully started.
Let’s launch our app and see what did we get.
As you can see, the actor system created an instance of the actor PlaybackActor
and sent it a Started
message. Also, keep in mind that the system message Started
will always be processed first. This means if you need to initialize your actor before starting the processing of custom messages. This code should be changed to the Started
message handler.
Restarting
If any failure occurs in the actor, the actor system will restart our actor to fix failure, to inform our actor of about an upcoming reboot, the actor system sends a Restarting
message to our actor.
Unlike the Started
message, working with the Restarting
message will be slightly different.
First of all, we need to create a new message called Recoverable
. This message will signal the child actor to generate an exception to simulate an actor’s failure. Next, create the child actor ChildActor
itself and add the code for processing the Restarting
and Recoverable messages to its ReceiveAsync()
method.
public Task ReceiveAsync(IContext context)
{
switch (context.Message)
{
case Restarting msg:
ProcessRestartingMessage(msg);
break;
case Recoverable msg:
ProcessRecoverableMessage(msg);
break;
}
return Task.CompletedTask;
}
private void ProcessRestartingMessage(Restarting msg)
{
ColorConsole.WriteLineGreen("ChildActor Restarting");
}
private void ProcessRecoverableMessage(Recoverable msg)
{
throw new Exception();
}
Now let’s change the `Playback Actor ' so that it can accept our new messages.
public class PlaybackActor : IActor
{
public PlaybackActor() => Console.WriteLine("Creating a PlaybackActor");
public Task ReceiveAsync(IContext context)
{
switch (context.Message)
{
case Started msg:
ProcessStartedMessage(msg);
break;
case PlayMovieMessage msg:
ProcessPlayMovieMessage(msg);
break;
case Recoverable msg:
ProcessRecoverableMessage(context, msg);
break;
}
return Task.CompletedTask;
}
private void ProcessStartedMessage(Started msg)
{
ColorConsole.WriteLineGreen("PlaybackActor Started");
}
private void ProcessPlayMovieMessage(PlayMovieMessage msg)
{
ColorConsole.WriteLineYellow($"PlayMovieMessage {msg.MovieTitle} for user {msg.UserId}");
}
private void ProcessRecoverableMessage(IContext context, Recoverable msg)
{
PID child;
if (context.Children == null || context.Children.Count == 0)
{
var props = Props.FromProducer(() => new ChildActor());
child = context.Spawn(props);
}
else
{
child = context.Children.First();
}
context.Forward(child);
}
In the ProcessRecoverableMessage
method, we define whether our class has a child actor, and if our actor doesn’t have child actors, we create these actors. If our actor has child actors, we extract their PID and store these PIDs in a variable. After we get the PID of the child actor, we send it the message Recoverable
, using the method context.Forward(child);
.
Now, all we have to do is change the `Program ' class so that it can support the child actor monitoring strategy. You will learn more about it in the next lessons, but for now, just copy the following code into your app.
class Program
{
static void Main(string[] args)
{
var system = new ActorSystem();
Console.WriteLine("Actor system created");
var props = Props.FromProducer(() => new PlaybackActor()).WithChildSupervisorStrategy(new OneForOneStrategy(Decider.Decide, 1, null));
var pid = system.Root.Spawn(props);
system.Root.Send(pid, new PlayMovieMessage("The Movie", 44));
system.Root.Send(pid, new PlayMovieMessage("The Movie 2", 54));
system.Root.Send(pid, new PlayMovieMessage("The Movie 3", 64));
system.Root.Send(pid, new PlayMovieMessage("The Movie 4", 74));
Thread.Sleep(50);
Console.WriteLine("press any key to restart actor");
Console.ReadLine();
system.Root.Send(pid, new Recoverable());
Console.ReadLine();
}
private class Decider
{
public static SupervisorDirective Decide(PID pid, Exception reason)
{
return SupervisorDirective.Restart;
}
}
}
When you run our test application, you will see that the child actor has been rebooted and informing you about it.
Stopping
To notify the actor that it will soon be stopped, the actor system sends him a Stopping
message.
Processing the actor stop may be necessary when you want to release the resources you are using. For example. These resources may be a connection to a database or an open file descriptor.
Let’s implement the processing of the message Stopping
in our actor. To do this, let’s add the Stopping
message processing to the ReceiveAsync()
method.
public Task ReceiveAsync(IContext context)
{
switch (context.Message)
{
case Started msg:
ProcessStartedMessage(msg);
break;
case PlayMovieMessage msg:
ProcessPlayMovieMessage(msg);
break;
case Stopping msg:
ProcessStoppingMessage(msg);
break;
}
return Task.CompletedTask;
}
And let’s implement method ProcessStoppingMessage()
. This methos be will display a stop message on the console.
private void ProcessStoppingMessage(Stopping msg)
{
ColorConsole.WriteLineGreen("PlaybackActor Stopping");
}
For stopping the actor, we need to use the RootContex.Stop()
method and pass it the PID of the actor we want to stop. Let’s change our Program
class so that it can stop our actor.
class Program
{
static void Main(string[] args)
{
var system = new ActorSystem();
Console.WriteLine("Actor system created");
var props = Props.FromProducer(() => new PlaybackActor()).WithChildSupervisorStrategy(new OneForOneStrategy(Decider.Decide, 1, null));
var pid = system.Root.Spawn(props);
system.Root.Send(pid, new PlayMovieMessage("The Movie", 44));
system.Root.Send(pid, new PlayMovieMessage("The Movie 2", 54));
system.Root.Send(pid, new PlayMovieMessage("The Movie 3", 64));
system.Root.Send(pid, new PlayMovieMessage("The Movie 4", 74));
Thread.Sleep(50);
Console.WriteLine("press any key to restart actor");
Console.ReadLine();
system.Root.Send(pid, new Recoverable());
Console.WriteLine("press any key to stop actor");
Console.ReadLine();
system.Root.Stop(pid);
Console.ReadLine();
}
private class Decider
{
public static SupervisorDirective Decide(PID pid, Exception reason)
{
return SupervisorDirective.Restart;
}
}
}
Now that we launch our application and press any key, we will see that our actor has received an alert about his emergency stop.