Using TPL for parallel programming is now a cup of cake. It made life easier for
those who are new to multithreaded programming. A bunch of code lines can be
encapsulated as an individual task using the Task class and you have the flexibility to run
the task in either Sync or Async mode. In Sync mode the UI freezes for a moment
until the task gets completed since all the other threads are blocked. So for a good
user experience you don't want to let your application screen freezes for a moment
so the 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 notified 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:
.ContinueWith((result) =>
{
//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's take an example of a 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 called ProcessFilesInParallel()
that will get enabled when the user selects the ParalledMode checkbox from the UI. The MaintainQuality Checkbox is toggling in two functions and the limit files
dropdown can limit the file processing and will Cancel the processing
immediately if the limit is reached. I'm not going to explain the other functional
code you can download the sample and analyze the code, but here we'll focus on updating the UI while a task is in progress and when the task gets completed.
The main task to perform is to show the progress bar progressing and show a analytical summary of the task when completed. Below is the complete code of
PracessfilesInParallel() method:
private void ProcessFilesInParallel()
{
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 the control.Invoke() method because if you don't use it'll throw an error about cross thread operation is invalid or something like that. so you
have to invoke the object in the currently executing thread.
Another important point is to use the TaskSchecutler.FromCurrentSynchronizationContext()
function as a parameter as this is the best fit to use if you are going to update the 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:
//update the progress on UI
progressBar1.Invoke((Action)delegate {
ReportProgress(count); });
Note - The sample attached has all the running code for you.
I hope you enjoyed this post cause I enjoyed creating this demo a lot.
Download the sample code.