Sunday, July 15, 2012

C# - Parallel Enumerating == Dangerous

When enumerating in parallel your enumerator get's called from different threads. Which is ofcourse logical and should not surprise. But you should also realize that the code before the yield statement (executed only once) is also called from a different thread as the code after the yield statement (also executed only once).

So if you have some setup and shutdown logic in your enumerable, make sure that that code does not assume that the calling thread will be identical. Even a try finally will be split and the try will be called from a different thread as the finally part.

And yes this is not theoretical as I used this code for my custom enumerable. Which crashes on the Monitor.Exit!

Monitor.Enter
try
{
yield code
}
finally
Monitor.Exit


The bug was caused by the Monitor.Exit being called from a different thread and therefore not exiting the lock that was entered. Nice!


The console app below demonstrates the effect;


namespace DummyConsoleApp
{
    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;


    namespace YieldFinallyTst
    {
        internal class Program
        {
            private static IEnumerable<int> Test()
            {
                Console.WriteLine("Enter Test()");
                try
                {
                    Console.WriteLine("ThreadId when starting enumeration = " + Thread.CurrentThread.ManagedThreadId);

                    for (var i = 0; i < 10; i++)
                    {

                        Console.WriteLine("ThreadId for value [" + i + "] = " + Thread.CurrentThread.ManagedThreadId);
                        yield return i;
                    }
                }
                finally
                {
                    Console.WriteLine("ThreadId when finishing enumeration = " + Thread.CurrentThread.ManagedThreadId);
                }
                Console.WriteLine("Finally Exit()");
            }

            private static void Main(string[] args)
            {
                Console.WriteLine("Main start");
                Parallel.ForEach(Test(), test => {/* nop */ });
                Console.WriteLine("Main stop");
                Console.ReadLine();
            }
        }
    }
}