While it can be complex at times to find these kinds of errors, in this case it was fairly easy to diagnose and fix, so following good bug fix practices I took the standard approach of writing a test to prove the bug exists, fixing the code and then running the test again to prove it’s fixed.
Now, it should be noted that testing threading issues in a deterministic way is nigh on impossible, and there is no guarantee that a unit test for threading issues will genuinely prove the code bug free, however the approach taken here was good enough to throw the threading exception each and every time I ran the test and also the throw the exception on the build server.
Here’s the code:
[TestMethod] public void ThreadingFun() { InitializeControllerAndGroup(); Task[] tasks = new Task[10] { Task.Factory.StartNew(() => MakeMove(1)), Task.Factory.StartNew(() => MakeMove(2)), Task.Factory.StartNew(() => MakeMove(1)), Task.Factory.StartNew(() => MakeMove(2)), Task.Factory.StartNew(() => MakeMove(1)), Task.Factory.StartNew(() => MakeMove(2)), Task.Factory.StartNew(() => MakeMove(1)), Task.Factory.StartNew(() => MakeMove(2)), Task.Factory.StartNew(() => MakeMove(1)), Task.Factory.StartNew(() => MakeMove(2)), }; Task.WaitAll(tasks); }
The important thing here is to see how easy it is to fire off a lot of threads in a single, easy to read unit test without all the usual threading plumbing code that would litter something like this.
The way it works is that we define a set of tasks via the Task Parallel Library (part of .NET 4.0) each of which calls the code where we have our threading problem. When Task.Factory.StartNew() is called the Task Parallel Library (TPL) immediately creates a new thread and calls the method returning control to our code along with a Task object so would can check the state of the task or cancel it if so desired. In this case we don’t care and immediately start another thread as soon as possible.
We then use the Task.WaitAll statement to wait until all the Tasks we defined are completed so that the test doesn’t complete prematurely. Too easy.
Note that we could also just as easily have used Parallel.Invoke for this. The same test using Parallel Invoke would be something like this:
[TestMethod] public void ParallelInvoke() { InitializeControllerAndGroup(); Parallel.Invoke( () => MakeMove(1), () => MakeMove(2), () => MakeMove(1), () => MakeMove(2), () => MakeMove(1), () => MakeMove(2), () => MakeMove(1), () => MakeMove(2), () => MakeMove(1), () => MakeMove(2) ); }
Regardless of the method you choose you can take advantage of the TPL to help you unit test your multithreaded code and make your application more resilient.
0 comments:
Post a Comment