Parallel code considerations
Parallel code is awesome and it gives a user the impression that the code is running much faster. A slow batch process can easily be made 10 times faster by moving the code into a parallel loop.
Writing parallel code in .NET is very easy if you have .NET 4.0 or above however there are a few things to be careful of. When you use the System.Threading.Tasks.Parallel class to create a For or ForEach loop, you are effectively telling the .NET framework to create multiple threads and process the code block simultaneously. You need to be careful that any shared objects you access inside the code block are thread safe. Here’s an example of some bad code:
[TestMethod] public void TestBadIncrementInThread() { int count = 0; int iterations = 1000; Parallel.For(0, iterations, i => { // run some code that takes a while e.g. call a web service var recordsAffected = 1; // webservice.callService(....); // update the total records affected count += recordsAffected; }); Assert.AreEqual(iterations, count, "Increment got lost."); }
If you run this test a few times you’ll notice that it fails. This is because the “count += recordsAffected” code is being run on multiple threads and one thread is overwriting the other.
The correct way to add a value to a shared variable (count
in our case) is with the System.Threading.Interlocked class e.g.
Interlocked.Add(ref count,
recordsAffected);
There are various
other useful methods in this class and you can find more about it at
https://msdn.microsoft.com/en-us/library/system.threading.interlocked(v=vs.110).aspx
Another mistake to be aware of is sharing database connections between threads. For example this code will randomly fail because the connection might be open while another thread is trying to use it.
[TestMethod] public void TestBadSharedContextInThread() { var random = new Random(); var context = new DbContext(); int iterations = 2; Parallel.For(0, iterations, i => { // run some code that takes a while e.g. call a web service // e.g. webservice.performUpdate(....); // simulate varying web service execution times between 1 and 5 seconds Thread.Sleep(random.Next(1000, 5000)); // get something from the db var firstLog = context.EventLogs.FirstOrDefault(); }); }
To fix this, the context needs to be created inside the Parallel.For code block.