Today I'm going to teach you how to cure one more fatal disease.
The symptoms:
- Multi-threaded code is irresponsive.
- Async operations don’t seem to be Asynchronous.
- Dead locks.
However, when a component does what you expect of it only ‘most of the time’, it’s a recipe for future failures such as slowness and in extreme cases even dead locks which can be extremely expensive when detected in production.
So what really happens when we call Start() for a task?
If we did not set an explicit scheduler, the task would run on its default scheduler, which is in the thread-pool, OR in the current context scheduler.
Current context scheduler? What is that?
It’s an internal property of the TPL framework, containing the scheduler running the current thread. When it exists and we start a new task without explicitly specifying which scheduler the task should use, it will run on the current thread scheduler.
Let's have a look:
This is an example of the issue with dead lock.
var qts = new System.Threading.Tasks.Schedulers.QueuedTaskScheduler(1);
TaskFactory tf = new TaskFactory(qts);
tf.StartNew(() =>
{
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
Task.Factory.StartNew(() => { manualResetEvent.Set(); });
manualResetEvent.WaitOne();
}).Wait();
Now that we think we understand how things work, let’s make them a little more complicated:
var qts = new System.Threading.Tasks.Schedulers.QueuedTaskScheduler(1);
TaskFactory tf = new TaskFactory(qts);
tf.StartNew(() =>
{
Task.Factory.StartNew(() => { }).Wait();
}).Wait();
By the same logic, this code should have been locked for the same reason. But it isn’t.
The reason for that is because the framework holds some “dark magic” behind the scenes.
This “dark magic” is Microsoft’s way of overcoming some of the inheritance problems that are caused by putting advanced tools in the hands of unexperienced programmers. The problem is that black magic doesn’t work with full scale applications. You need to fully understand what’s going on under the hood when writing good code, assuming that you care about quality and not just about your LinkedIn profile.
Don't get me wrong, TPL is great and I will continue using it whenever I can. However, I will never, under no circumstance write this code:
Task.Factory.StartNew(() => { });
I will use instated:
new Task(() => { }).Start(TaskScheduler.Default);
Some very complex regex can fix my project, but I rather do it the hard way and fix it manually.
Remember it's not just ‘Task.Factory.StartNew’ it happens in ‘ContinueWith’ as well.