Writing high performance parallel code with monitors [EN]

Multithread application is going to be next issue for developer, and we need to be ready for this big change. In .NET Framework 4, Microsoft introduced Task Parallel Library, a set of API that helps developer creating concurrent applications. That’s means that you don’t have to take care about all those concurrent issues anymore, its mean now is easier to create concurrent code.

Currently I’m working in an application that makes a hard use of all TLP code. What I do is creating a lot of Task objects to manage the executing of a set of rules that have to be executed for each Uri. I maintain a list with all the Task object to observer during time, how much Task are now running, and make some statistics about software speed.

This means I have UI, created in WPF that need to communicate with this code to get all running Task and display this information in the UI. Since I’m using a simple List<Task> to get a reference to all task created during my application execution, I need a way to lock and add Tasks to this list, enumerate this list and remove Tasks. But I want this lock to be shortest one and only lock to important operations like add and remove items when Task are finished.

So I have two important operations, create new Task and add to this list, and when the Task is finish removes this Task from the list, and for that I need to wait if the lock is acquired by other thread, because multiple Tasks can finish at the same time.

Creating task is done by a timer inside my application and this timer is set to run each second to check if the running Task in the system are lower that minimum Task running. If this is true is the moment create more Task and add to the list. Since this timer is executed every second I’ll need to lock my list of Task, do my work and then release the lock. I’m using the list as lock object. But this timer can wait more than one second until the lock is released, because other Task are now executing and finish work and remove items from my list, so It’s mean I’m creating a classic convoy in my application. Every time my timer method is executed and wait until the lock to release, this time is bigger that timer elapsed time, so I need a way to wait only a certain period of time.

What I do in this method is using the new API for Monitor in .NET 4 that attempts, for a number of milliseconds, to acquire an exclusive lock and atomically set a value that indicates whether the lock was taken or not. In my case I set these milliseconds in 400, because in this method I do more stuff than work with my list of Task. If the lock is bigger than 400ms the lock will not be acquired.

bool taken = false;
try
{
    Monitor.TryEnter(task, 400, ref taken);
    if (taken)
    {
        if (!cancelationTokenSource.IsCancellationRequested)
        {
            var count = (from p in task
                         where p.Status == TaskStatus.Running || p.Status == TaskStatus.WaitingToRun
                         select p).Count();
        }
    }
}
finally
{
    if (taken)
    {
        Monitor.Exit(task);
    }
}

As you can see in this code I’m calling Monitor.TryEnter inside a try/catch block, and then in the finally block if the lock was acquired I release the lock by calling Monitor.Exit. I do in that way because if during the execution of my code an exception is throw, the call to Monitor.Exit at the end of the code will not be executed anymore, causing that I have a lock that will never be released.

My other scenario is when I want to get all Task to count how much task are now running, I do the same inside get accessor of the property Task, but in this case I only wait for 250ms instead of 400ms. If the lock is acquired I copy the collection into a new list and there I can safety count how much Task are running.

public List<Task> Tasks
{
    get
    {
        bool taken = false;
        List<Task> list = null;
        try
        {
            Monitor.TryEnter(task, 250, ref taken);
            if (taken)
            {
                list = new List<Task>(task.ToArray());
            }
        }
        finally
        {
            if (taken)
            {
                Monitor.Exit(task);                         
            }
        }
        return list;
    }
}

 

This solution is simple and keeps your code with the lowest contention rate giving boots to those important operations that need to wait until the lock is released.

I could also create a wrap over List<T> list using ReaderWriterLockSlim to control read and write operation during lock, but ReaderWriterLockSlim is focus on multiple readers and only one writer, and I my application I have multiple writers and only one reader.

All the best.

Luis.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *