Parallel tasks are important to complete the independent operation simultaneously. It makes us completely very heavy and time taking processes in less time when these operations run in multi-core environment but we have to follow the rules to make this multi-tasking to work.
Today I experienced such a case where my written code put me in a condition of “Dead Lock”. It is a situation more than one task that want to acquire the same resource and one is already holding this resource but another also wants to access it. If no task able to complete its operation then it creates a deadlock situation.
When we creating application, in some cases it may be possible to update the UI in various threads. E.g. Progress reporting. I found a case that let our application put into a situation which freezes the application. Let there was few tasks that are independent of each other but we need to merge the result all of these tasks with some required condition.
List<Task> tasks = new List<Task>();
//Create and start independent tasks
tasks.Add(Task.Factory.StartNew<List<string>>(() => CreateInitialData()));
tasks.Add(Task.Factory.StartNew<List<string>>(() => CreateValidationData()));
tasks.Add(Task.Factory.StartNew<List<string>>(() => CreateGUILookup()));
Now we need to complete these tasks to get the results so that we can merge it. To do this, the Task Parallel Library has Task.WaitAll method which takes array of task as a parameter. It waits until all the operations do not complete. In other words, it hold the UI and waits to complete all the tasks.
Task.WaitAll(tasks.ToArray());
//Merge result of all task on completion
In one of the thread, some subtask is done which make a call to the UI. It was working fine for a while until I made some changes. I was trying to get one property of the progress bar and it was accessed in an asynchronous way using the “BeginInvoke” method. It was a bug in the application which does not return the correct results so I replace it with the “Invoke” method.
“Control.Invoke()” method waits and returns the results immediately while BeginInvoke method is an asynchronous approach. It leaves control from the current statement without returning the values and we are not able to get returned results until we call AsynResult.EndInvoke().
//At some instance code by mistake try to access
// the properties of the any control and made dead lock
//
int progress = (int)this.Invoke((Func<int>)delegate
{ return progressBar1.Value; });
System.Diagnostics.Debug.Write(progress);
Now what WaitAll does, it blocks until all tasks have finished. The tasks can't finish because Invoke will run its action on the UI thread, which is already blocked by WaitAll. It is creating deadlock because Invoke waits for the UI thread to unblock.
Complete example:
namespace ParallelWaitDeadLock
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
List<Task> tasks = new List<Task>();
//Create and start indepenet tasks
tasks.Add(Task.Factory.StartNew<List<string>>(() => CreateInitialData()));
tasks.Add(Task.Factory.StartNew<List<string>>(() => CreateValidationData()));
tasks.Add(Task.Factory.StartNew<List<string>>(() => CreateGUILookup()));
Task.WaitAll(tasks.ToArray());
//Merge result of all task on completion
}
private List<string> CreateInitialData()
{
//
Task.Delay(1000);
return new List<string>();
}
private List<string> CreateGUILookup()
{
//
Task.Delay(1000);
//At some instance code by mistake try to access
// the properties of the any control and made dead lock
//
int progress = (int)this.Invoke((Func<int>)delegate
{ return progressBar1.Value; });
System.Diagnostics.Debug.Write(progress);
return new List<string>();
}
private List<string> CreateValidationData()
{
//
Task.Delay(1000);
return new List<string>();
}
}
}
We need to take care to avoid such a situation when subtask tries to do UI relation operations even the parent task holds the UI and waits to complete the task.
It is causing a deadlock. The UI thread is waiting for the all tasks to be completed. UI resources are already locked by the Task.WaitAll and then we are trying to invoke code on the UI thread which is causing "Deadlock".
Task.WhenAll() method will return a new task that will be marked as completed when all tasks have completed. If we await this task, UI thread will be freed, and so the routine will be able to invoke code on the UI thread, avoiding a deadlock.
We should use Task.WhenAll() instead.
await Task.WhenAll(tasks.ToArray());