Wednesday, 7 September 2011

Synchronize UI when task is in progress or completed TPL .Net 4.0

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:
.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 take an example of simple windows forms application that can process images to generate the thumbnails:


First let design the UI like this:

image

[I’m still using WinForms just to save my time in creating demos Smile with tongue out.]

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:

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 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.

image

So when you’ll run the code the output would be something like this:

image

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 have all the running code for you.

SNAGHTML192c8af4

I hope you enjoyed this post cause I enjoyed lot creating this demo. Smile

Download the complete sample source code in the article