Using TPL for parallel programming is now a cup of cake. It made life easier for those who are new to multithreaded programming. Bunch of code lines can be encapsulated as an individual task using Task class and you are flexible to run the task either in Sync or Async mode. In Sync mode the UI freeze for moment until the task gets completed since all other threads are blocked. So for a good user experience you don’t want to let your application screen freezes for moment so Async task is a good choice in such case.
This post is to show how to notify the UI when the Asycn task gets completed. Absolutely you will want to get notify when you Asycn task has completed its work/execution.
Task.ContinueWith() is the function that will execute the next task after completion of the invoking task.
Syntax:{
//UI updation code to perform
}, new CancellationTokenSource().Token, TaskContinuationOptions.None,
//Right way to synchronize the UI with the completion of invoking task on ContinueWith
TaskScheduler.FromCurrentSynchronizationContext());
Let take an example of simple windows forms application that can process images to generate the thumbnails:
First let design the UI like this:
[I’m still using WinForms just to save my time in creating demos .]
In the Form1.cs code behind file I’ve create a function ProcessFilesInParallel() that will get enabled when user select the ParalledMode checkbox from the UI. Rest MaintainQuality Checkbox is toggling in two functions and limit files dropdown can limit you file processing and will Cancel the processing immediately the limit reaches. I’m not going to explain the other functional code you can download the sample and do analyze the code here but we’ll focus on UI updation while task in progress and when the task gets complete.
The mail task to perform is to show the progress bar progressing and Show tittle analytical summary of the task when completed. Below is the complete code of PracessfilesInParallel() method:
{
ParallelOptions parOpts = new ParallelOptions();
parOpts.CancellationToken = cancelToken.Token;
parOpts.MaxDegreeOfParallelism = Environment.ProcessorCount;
string[] files = Directory.GetFiles(sourcePath, "*.jpg");
long count = 0;
totalfiles = files.Length;
btnCancel.Visible = true;
//--- Record the start time---
DateTime startTime = DateTime.Now;
try
{
Task t1 = Task.Factory.StartNew(() =>
{
try
{
Parallel.ForEach(files, parOpts, currentFile =>
{
//Check if cancellation requested
if (cancelToken.Token.IsCancellationRequested)
{
cancelToken.Token.ThrowIfCancellationRequested();
}
string filename = Path.GetFileName(currentFile);
//Threadsafe updation to shared counter
count = Interlocked.Increment(ref count);
if (islimited && fileLimit <= count)
{
cancelToken.Cancel();
// MessageBox.Show("Limit reached fileLimit = " + fileLimit + " Count=" + count);
}
if (isQuality)
GenerateThumbnail(filename);
else
GetThumb(filename);
//update the progress on UI
progressBar1.Invoke((Action)delegate { ReportProgress(count); });
});
}
catch (OperationCanceledException ex)
{
progressBar1.Invoke((Action)delegate { ReportProgress(0); });
MessageBox.Show(ex.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
//ContinueWith is used to sync the UI when task completed.
}, cancelToken.Token).ContinueWith((result) =>
{
//Note the time consumed here
TimeSpan elapsed = DateTime.Now.Subtract(startTime);
TimeElapsed = (int)elapsed.TotalSeconds + " s : " + elapsed.Milliseconds + " ms";
//finally update the UI with the summary result
lblResult.Invoke((Action)delegate { lblResult.Text = "File Processed: " + count + " out of " + fileLimit + " Time Elapsed: " + TimeElapsed; });
},new CancellationTokenSource().Token, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (AggregateException ae)
{
MessageBox.Show(ae.InnerException.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
As you can see I’ve used control.Invoke() method because if you don’t use it’ll throw error of cross thread operation is invalid or something like that. so you have invoke the object in the currently executing thread.
Other important is to use TaskSchecutler.FromCurrentSynchronizationContext() function as parameter as this is the best fit to use if you are going to update UI in the ContinueWith() task delegate.
So when you’ll run the code the output would be something like this:
The code to show progress is also written in the function ProcessinParallel() method:
progressBar1.Invoke((Action)delegate { ReportProgress(count); });
Note - The sample attached have all the running code for you.
I hope you enjoyed this post cause I enjoyed lot creating this demo.
Download the complete sample source code in the article
thanks a lot for the tip with Invoke. i was getting cross thread and was having a hard time solving it. your tip with invoke solved the issues. cheers
ReplyDeleteany solution about it using .NET 3.5 ? thx
ReplyDelete