Click here to Skip to main content
15,881,600 members
Articles / All Topics

PostSharpin’ Part 2 – Actor

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
5 May 2014CPOL5 min read 6.6K   1  
In Part 1 I looked at PostSharp’s support for INotifyPropertyChanged, and several handy aspects to help with threading: Background, Dispatch, ThreadUnsafe and ReaderWriterSynchronized.

In Part 1 I looked at PostSharp’s support for INotifyPropertyChanged, and several handy aspects to help with threading: Background, Dispatch, ThreadUnsafe and ReaderWriterSynchronized. In part 2 I’d planned to look at PostSharp’s Actor support and new features for undo/redo, but life got in the way, so part 2 will cover only the Actor aspect, and part 3 will cover new features in PostSharp 3.2.

Actor

The Actor model hasn’t yet received a lot of attention in the .NET world. The model was first defined in 1973 as a means to model parallel and distributed systems, “a framework for reasoning about concurrency.” The model assumes that “concurrency is hard” and provides an alternative to do-it-yourself threading and locking. It’s built into languages like Erlang and Scala, and there are a number of libraries and frameworks. It’s gotten a recent boost in the .NET world with F# agents, the TPL Dataflow library and Project Orleans.

Conceptually, an actor is a concurrency primitive which can both send and receive messages and create other actors, all completely asynchronous, and thread-safe by design. An actor may or may not hold state, but it is never shared.

Where does PostSharp fit in? Remembering the PostSharp promise: “Eradicate boilerplate. Raise abstraction. Enforce good design.” the PostSharp Actor implementation allows developers to work at the “right” level of abstraction, and provides both build time and run time validation to avoid shared mutable state and ensure that private state is accessed by only a single thread at a time.

To use the Actor aspect, install the Threading Pattern Library package from NuGet.

Ping Pong

I started with the PingPong sample (well, PingPing really) from PostSharp. Here’s the code:

[Actor]
public class Player 
{
    private string name;
    private int counter;

    public Player(string name)
    {
        this.name = name;
    }

    public async Task Ping(Player peer, int countdown)
    {
        Console.WriteLine("{0}.Ping({1}) from thread {2}", this.name, countdown,
                          Thread.CurrentThread.ManagedThreadId);

        if (countdown > 1)
        {
            await peer.Ping(this, countdown - 1);
        }

        this.counter++;
    }

    public async Task GetCounter()
    {
        return this.counter;
    }
}

class Program
{
    static void Main(string[] args)
    {
        AsyncMain().Wait();
        Console.ReadLine();
    }

    private static async Task AsyncMain()
    {
        Console.WriteLine("main thread is {0}", Thread.CurrentThread.ManagedThr

        Player ping = new Player("Sarkozy");
        Player pong = new Player("Hollande");

        Task pingTask = ping.Ping(pong, 10);

        await pingTask;

        Console.WriteLine("{0} Counter={1}", ping, await ping.GetCounter());
        Console.WriteLine("{0} Counter={1}", pong, await pong.GetCounter());
    }
}

Here the Player class is an actor, and decorated with the PostSharp Actor aspect. The “messages” are implied by the Ping and GetCounter async methods. Whether the “message-ness” of the actor model should be abstracted away is certainly a point for discussion, but it does provide for easier programming within an OO language like C#.

From the output we see that 1) activation (construction) is performed on the caller’s thread, 2) the player’s methods are invoked on background threads, and 3) there is no thread affinity.
pingpong_sm

Validation

The compile-time validation performed when using the Actor aspect tries to ensure you do the right thing.

1. All fields must be private, and private state must not be made available to other threads or actors.

If we try to define the name field as public:

[Actor]
public class Player 
{
    public string name;
    private int counter;
    ...
} 

This results in the compiler error: Field Player.name cannot be public because its declaring class Player implements its threading model does not allow it. Apply the [ExplicitlySynchronized] custom attribute to this field to opt out from this rule.

The same holds true of a public property:

[Actor]
public class Player 
{
    ...
    public int Id { get; private set; }
    ...
}

This results in the compile-time error: Method Player cannot return a value or have out/ref parameters because its declaring class derives from Actor and the method can be invoked from outside the actor.

2. All methods must be asynchronous.

To PostSharp this means that method signatures must include the async modifier. If you try to return a Task from a non-async method, something like this:

public Task<string> SayHello(string greeting)
{
    return Task.FromResult("You said: '" + greeting + "', I say: Hello!");
}

You’ll get a compiler error: Method Player cannot return a value or have out/ref parameters because its declaring class derives from Actor and the method can be invoked from outside the actor.

The async rule also means that you must ignore the standard compiler warning about using async when you don’t demonstrably need to, which is why the GetCounter method looks like this:

public async Task<int> GetCounter()
{
    return this.counter;
}

PostSharp will dispatch the method to a background task, so you should ignore the compiler warning: This async method lacks ‘await’ operators and will run synchronously. Consider using the ‘await’ operator to await non-blocking API calls, or ‘await Task.Run(…)’ to do CPU-bound work on a background thread.

If you remove the async modifier the Actor validation will fail. You can add an await, but it looks silly, and you shouldn’t await Task.FromResult anyway:

public async Task<int> GetCounter()
{
    return await Task.FromResult<int>(this.counter);
}

You can, however, write a synchronous method, which PostSharp will dispatch to a background thread. For example:

public void Ping(Player peer, int countdown)
{
    Console.WriteLine("{0}.Ping from thread {1}", this.name,
                      Thread.CurrentThread.ManagedThreadId);

    if (countdown >= 1)
    {
        peer.Pong(this, countdown - 1);
    }

    this.counter++;
}

This may be a good thing, but also possibly misleading, since at first glance a developer might assume the method is executed synchronously on the current thread.

Rock-Paper-Scissors

Next I tried the “Rock-Paper-Scissors” example as described here.

Here’s my implementation.

namespace Roshambo
{
    public enum Move
    {
        Rock,
        Paper,
        Scissors
    }

    [Actor]
    public class Coordinator
    {
        public async Task Start(Player player1, Player player2, int numberOfThrows)
        {
            Task.WaitAll(player1.Start(), Task.Delay(10), player2.Start());

            while (numberOfThrows-- > 0)
            {
                var move1Task = player1.Throw();
                var move2Task = player2.Throw();
                Task.WaitAll(move1Task, move2Task);

                var move1 = move1Task.Result;
                var move2 = move2Task.Result;

                if (Tie(move1, move2))
                {
                    Console.WriteLine("Player1: {0}, Player2: {1} - Tie!", move1, move2);
                }
                else
                {
                    Console.WriteLine("Player1: {0}, Player2: {1} - Player{2} wins!", move1, move2,
                        FirstWins(move1, move2) ? "1" : "2");
                }
            }
        }

        private bool Tie (Move m1, Move m2) {
            return m1 == m2;
        }

        private bool FirstWins(Move m1, Move m2)
        {
            return
              (m1 == Move.Rock && m2 == Move.Scissors) ||
              (m1 == Move.Paper && m2 == Move.Rock) ||
              (m1 == Move.Scissors && m2 == Move.Paper);
        }

    }

    [Actor]
    public class Player
    {
        private Random _random;
        private string _name;

        public Player(string name)
        {
            _name = name;
        }
        public async Task Start()
        {
            int seed = Environment.TickCount + System.Threading.Thread.CurrentThread.ManagedThreadId;
            _random = new Random(seed);
        }

        public async Task<Move> Throw()
        {
            return (Move)_random.Next(3);
        }

        public async Task<string> GetName()
        {
            return _name;
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        AsyncMain().Wait();
        Console.ReadLine();
    }

    private static async Task AsyncMain()
    {
        var coordinator = new Coordinator();
        var player1 = new Player("adam");
        var player2 = new Player("zoe");

        await coordinator.Start(player1, player2, 20);
    }
}

And the exciting results:
rps

A few things to note:

  • I passed a name to the Player constructor but then never used it again. As private state, to access the name you must follow the Actor message rules and use an async method. I wouldn’t want the Coordinator to repeatedly ask each Player for its name, but this could have been done once at play start.
  • Trying to uniquely seed a System.Random instance for each player was tricky, and my implementation is a hack. The Random class is not thread-safe, so while sharing a single static Random instance among Player actors is an option, having to perform my own locking around Random.Next calls seemed to violate the spirit of the actor model. The default seed for a Random instance is Environment.TickCount, which if called in close succession will likely return the same value. Using the current thread id as a seed is an alternative, but although PostSharp will ensure that Actor methods will be called on a background thread, there’s no assurance they’ll be different threads for different actor instances. My not-so-robust compromise was to take the sum of TickCount and thread id and cross my fingers. Including the dummy Task.Delay when waiting for the players to start helps.
  • The Coordinator here does not hold state, and its Start method will 1) tell the players to start, 2) tell the players to throw, and 3) announce the result.
  • The Player does hold non-shared state, and contains Start, Throw and GetName async methods. None of these methods is inherently asynchronous, so I see compiler warnings telling me to consider using the await operator. I could have made these methods synchronous, but as I said above I think it leads to some cognitive dissonance between the code you see and the underlying actor implementation.

Summary

Overall, despite some quirks, using the Actor aspect could be useful. It would be interesting to compare PostSharp’s Actor support with other .NET implementations, and I may try that some day.


Filed under: c# Tagged: Actor, AOP, PostSharp, Threading

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I started my professional career as a mainframe Assembler programmer at a small insurance company. Since then I've developed dozens of enterprise and commercial solutions using a grab bag of technologies. I've been developing with .NET since 2003 and still love it.

Comments and Discussions

 
-- There are no messages in this forum --