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();
}
}
}
}