Monday, 1 May 2017

TPL 08 Barriers

Just as before when dealing with threads, when it comes to tasks, barriers are a rendezvous point where multiple tasks running in parallel can meet up before continuing, this is very similar to the CountdownEvent class. Barriers come with a couple of advantages, for example they are more appropriate if you may have to run multiple phases of a countdown, meaning that if you run a countdown event down that's however with barriers if after you signal your barrier down to 0 and signal it again you'll reset the barrier.

let's take a look at the following example, here I've create a guess the number game in which each participant guesses a number on a separate thread.

class Person
{
    static Random rnd = new Random(DateTime.Now.Millisecond);
    public string Name { get; set; }
    public Person(string Name) { this.Name = Name; }
    public int GuessNumber(int UpperLimit)
    {
        Task.WaitAll(Task.Delay(rnd.Next(1000, 2000)));
        var guess = rnd.Next(UpperLimit);
        Console.WriteLine($"{Name} guessed {guess}");
        return guess;
    }

}

Easy enough, just a person class that guesses a number, next i made an abstract Guess the number class

abstract class GuessTheNumber
{
    public int UpperLimit { get; set; }
    public int TheNumber { get; set; }
    public Person[] Participants { get; set; }
    public GuessTheNumber(int TheNumber, Person[] Participants, int UpperLimit = 100)
    {
        this.TheNumber = TheNumber;
        this.Participants = Participants;
        this.UpperLimit = UpperLimit;
    }
    public abstract Person[] Start();

}

because I'm lazy and i want to create to implementations of my start function, one using the CountdownEvent and the other using barriers to explore the differences. However before we look at our different implementations of the start function, let's look at our main.

class Program
{
    static void Main(string[] args)
    {
        var ppl = new Person[] {
            new Person("Pawel"), new Person("Magda"),
            new Person("Marin"), new Person("Trish"),
            new Person("Jakub"), new Person("Kelly"),
            new Person("Tomek"), new Person("Natly") };


        var gtnGame = new GuessTheNumberCDE(7, ppl, 10);
        //var gtnGame = new GuessTheNumberBarrier(7, ppl, 10);
        var winners = gtnGame.Start();

        foreach (var w in winners)
            Console.WriteLine($"{w.Name} Guessed right");
    }
}

pretty straight forward, we just swap between our barriers and countdown event implementations. first let's take a look at the countdown event implementation.

class GuessTheNumberCDE : GuessTheNumber
{
    public GuessTheNumberCDE(int TheNumber, Person[] Participants, int UpperLimit = 100)
        : base(TheNumber, Participants, UpperLimit) { }

    public override Person[] Start()
    {
        List<Person> winners = new List<Person>();
        object _lock = new object();
        int phase = 0;

        using (var cdeHandler = new CountdownEvent(Participants.Count()))
            while (winners.Count == 0)
            {
                foreach (var participant in Participants)
                    Task.Factory.StartNew<int>(p =>
                    {
                        var person = p as Person;

                        return person.GuessNumber(UpperLimit);
                    }, participant).ContinueWith<int>((tr, p) =>
                    {
                        if (tr.Result == TheNumber)
                            lock (_lock)
                                winners.Add(p as Person);
                        cdeHandler.Signal();
                        return -1;
                    }, participant);

                cdeHandler.Wait();
                if (winners.Count() == 0)
                    cdeHandler.Reset();
                Console.WriteLine($"Phase {phase++}");
            }

        return winners.ToArray();
    }
}

a couple of things you can notice right off the bat, we need a phase variable to keep track of how many iterations of guessing our participants went through, another key difference is that the Countdown event implementation has to explicitly reset itself. Now let's take a look at the barrier class.

class GuessTheNumberBarrier : GuessTheNumber
{
    public GuessTheNumberBarrier(int TheNumber, Person[] Participants, int UpperLimit = 100)
        : base(TheNumber, Participants, UpperLimit) { }

    public override Person[] Start()
    {
        List<Person> winners = new List<Person>();
        object _lock = new object();

        using (var barrier = new Barrier(Participants.Count() + 1,
            b => Console.WriteLine($"Phase {b.CurrentPhaseNumber}")))
            while (winners.Count < 1)
            {
                foreach (var participant in Participants)
                    Task.Factory.StartNew<int>(p =>
                    {
                        var person = p as Person;

                        return person.GuessNumber(UpperLimit);
                    }, participant).ContinueWith<int>((tr, p) =>
                    {
                        if (tr.Result == TheNumber)
                            lock (_lock)
                                winners.Add(p as Person);
                        barrier.SignalAndWait();
                        return -1;
                    }, participant);

                barrier.SignalAndWait();
            }

        return winners.ToArray();
    }

}

notice that when we instantiate the barrier we can create a post phase delegate that can do something every time our barrier is signaled down to 0. We also don't have to explicitly reset our barrier, we can just call signalAndWait an extra time and our barrier resets itself.