I wanted to try out Threading.Task (C#) to run some work in parallel. In this simple example I have a form with progress bar and button. On click the RunParallel function is called. Without Task.WaitAll() it seems to run through fine. However, with the WaitAll statement the form shows and nothing happens. I dont understand what I am doing wrong in the setup below.
Thanks in advance.
public partial class MainWindow : Form
{
public delegate void BarDelegate();
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, EventArgs e)
{
RunParallel();
}
private void RunParallel() {
int numOfTasks = 8;
progressBar1.Maximum = numOfTasks;
progressBar1.Minimum = 0;
try
{
List<Task> allTasks = new List<Task>();
for (int i = 0; i < numOfTasks; i++)
{
allTasks.Add(Task.Factory.StartNew(() => { doWork(i); }));
}
Task.WaitAll(allTasks.ToArray());
}
catch { }
}
private void doWork(object o1)
{
// do work...
// then
this.Invoke(new BarDelegate( UpdateBar ));
}
private void UpdateBar()
{
if (progressBar1.Value < progressBar1.Maximum) progressBar1.Value++;
}
}
You are waiting on the UI thread. This freezes the UI. Don't do that.
In addition, you are deadlocking because Invoke
waits for the UI thread to unblock.
My advice: use async/await
if at all possible.
And please do not swallow exceptions. You're creating future work for yourself that way.
That's 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
To make your code really run asynchronously, try something like this:
private void RunParallel() {
int numOfTasks = 8;
progressBar1.Maximum = numOfTasks;
progressBar1.Minimum = 0;
try
{
var context=TaskScheduler.FromCurrentSynchronizationContext()
for (int i = 0; i < numOfTasks; i++)
{
Task.Factory.StartNew(()=>DoWork(i))
.ContinueWith(()=>UpdateBar(),context);
}
}
catch (Exception exc)
{
MessageBox.Show(exc.ToString(),"AAAAAARGH");
}
}
private void DoWork(object o1)
{
// do work...
}
private void UpdateBar()
{
if (progressBar1.Value < progressBar1.Maximum) progressBar1.Value++;
}
In this case, UpdateBar is called on the UI context each time a task finishes without causing any blocking.
Note, this is not production code, just a way to show how you can run a method asynchronously and update the UI without blocking. You do need to read about Tasks to understand what they do and how they work.
Using async/await
in .NET 4.5+ you can write this in a much simpler way. The following will execute DoWork in the background without blocking, but still update the UI each time a Task finishes.
private async void button1_Click(object sender, EventArgs e)
{
int numOfTasks = 8;
progressBar1.Maximum = numOfTasks;
progressBar1.Minimum = 0;
try
{
for (int i = 0; i < numOfTasks; i++)
{
await Task.Run(() => DoWork(i));
UpdateBar();
}
}
catch (Exception exc)
{
MessageBox.Show(exc.ToString(), "AAAAAARGH");
}
}
await
tells the compiler to generate code to execute anything below it ont the original (UI) threadwhen the task to its right finishes:
In doWork you call this.Invoke(...)
which waits for UI thread to process messages. Unfortunatelly you UI thread is not processing messages because it is waiting for all doWork(...)
to finish.
Easiest fix is to change this.Invoke
to this.BeginInvoke
(it will send messages but no wait for them to be processed) in doWork.
Although, I have to admin it is still not by-the-book as UI should not wait for anything.
Simple pattern (pre async/await era):
Task.Factory.StartNew(() => {
... work ...
})
.ContinueWith((t) => {
... updating UI (if needed) ...
}, TaskScheduler.FromCurrentSynchronizationContext());
RunParallel
blocks until all the tasks are completed. Use a different mechanism to notify the UI.