Pages

Search

Saturday, March 31, 2012

Thread Synchronization Context


Below example demonstrates, updating UI the status of background running long process.
This can also be implemented using delegates by invoking the actual method which deals with background job and updating the status in the UI which is running in a different thread.
Since the UI is running in a different thread than in which the actual background job is working, while updating it is required to perform invoking through delegate like “MethodInvoker”.
Beside this, below example also updates the status in UI, but by using the Synchronization context of UI Thread.
DB.cs
The actual background job
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace InsertBulkRecords
{
    public class InsertStatus
    {
        public int CurrentRecord;
        public int MaxRecords;
        public bool Completed = false;
        public bool Cancelled = false;
        public string Error;
    }
    public class DB
    {
        SynchronizationContext objUIContext = null;
        SendOrPostCallback objUIUpdate = null;
        CancellationToken objTkn;
        Task objTask = null;

        //Insert function can also be defined as below
        //public void Insert(CancellationToken tkn, SynchronizationContext argUIContext, SendOrPostCallback argUIUpdate)

        public void Insert(CancellationToken tkn, SynchronizationContext argUIContext, Action<Object> argUIUpdate)
        {
            objTkn = tkn;
            objUIUpdate = new SendOrPostCallback(argUIUpdate);
            //Incase if the last argument type is "SendOrPostCallback" then above line should be commented
            //and below line should be uncommented
            //objUIUpdate=argUIUpdate

            objUIContext = argUIContext;
            //registering to invoke a function, when the cancellation token is cancelled
            tkn.Register(delegate
            {
                objUIContext.Send(objUIUpdate, new InsertStatus()
                {
                    Cancelled = true,
                    CurrentRecord = 0,
                    MaxRecords = 0
                });
            });

            //starting the task invoking the "InsertBackend" function
            objTask = Task.Factory.StartNew(() => { InsertBackend(); }, tkn);

            //making below task to continue when there is any
            //fault during the execution of the task
            objTask.ContinueWith(new Action<Task>((t) =>
            {
                objUIContext.Post(objUIUpdate, new InsertStatus()
                {
                    CurrentRecord = 0,
                    MaxRecords = 0,
                    Error = t.Exception.GetBaseException().Message
                });
            }), TaskContinuationOptions.OnlyOnFaulted);

            //making below task to continue when the task is
            //completed
            objTask.ContinueWith((t) =>
            {
                if (objTkn.IsCancellationRequested)
                    return;
                objUIContext.Post(objUIUpdate, new InsertStatus()
                {
                    Completed = true
                });
            }, TaskContinuationOptions.OnlyOnRanToCompletion);
        }

        //The function which inserts records (taking time).
        private void InsertBackend()
        {
            int maxRecords = 60;
            int recordIndex = 0;
            int recWaitTime = 100;
            while (recordIndex < maxRecords)
            {
                Thread.Sleep(recWaitTime);
                recordIndex++;
                //checking if the token cancellation is requested
                if (objTkn.IsCancellationRequested)
                    return;
                objUIContext.Send(objUIUpdate, new InsertStatus() { CurrentRecord = recordIndex, MaxRecords = maxRecords });
                //throwing exception, for specific record. The UI should be updated saying as failed
                //this helps to check if the "fault" continution task is getting invoked
                if (recordIndex == 1000)
                    throw new Exception("error while inserting 1000 th record");
            }
        }
    }
}

UI (Form.cs)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.Threading;
using InsertBulkRecords;
namespace InsertBulkRecords_UI
{
    public partial class frmInsertRecords : Form
    {
        public frmInsertRecords()
        {
            InitializeComponent();
        }

        CancellationTokenSource cancellationSrc = new CancellationTokenSource();

        private void btnInsert_Click(object sender, EventArgs e)
        {
            try
            {
                InsertBulkRecords.DB objDB = new DB();
                cancellationSrc = new CancellationTokenSource();
                //below code to call the insert function and send the "SendOrPostCallback" delegating the "UpdateUI" function
                //"Insert" function should also be expecting the "SendOrPostCallback" type argument for the last
                //argument
                //objDB.Insert(cancellationSrc.Token, SynchronizationContext.Current, new SendOrPostCallback(UpdateUI));

                //calling the insert function sending the function using lamda expression
                //objDB.Insert(cancellationSrc.Token, SynchronizationContext.Current, (o) => {
                //    //write function to type cast "o" and perform UI Update
                //});

                //calling the insert function sending the function by lamda expression  delegting(Action) function
                //since "Action<Object>" delegate is same as "SendOrPostCallback" type
                //"Insert" function should also be expecting the "Action<Object>" type argument for the last
                //argument
                //objDB.Insert(cancellationSrc.Token, SynchronizationContext.Current, new Action<Object>((o) =>
                //{
                //write function to type cast "o" and perform UI Update
                //}));

                //calling the insert function sending the function by delegating
                objDB.Insert(cancellationSrc.Token, SynchronizationContext.Current, delegate(object o)
                {
                    {
                        try
                        {
                            InsertStatus argInsertStatus = o as InsertStatus;
                            if (argInsertStatus.Error != null && argInsertStatus.Error.ToString().Trim().Length > 0)
                            {
                                lblStatus.Text = "Error : " + argInsertStatus.Error;
                                prgInsertStatus.Value = 0;
                            }
                            else if (argInsertStatus.Completed)
                            {
                                lblStatus.Text = "Completed";
                                prgInsertStatus.Value = 0;
                            }
                            else if (argInsertStatus.Cancelled)
                            {
                                lblStatus.Text = "Cancelled";
                                prgInsertStatus.Value = 0;
                            }
                            else
                            {
                                prgInsertStatus.Value = argInsertStatus.CurrentRecord;
                                prgInsertStatus.Maximum = argInsertStatus.MaxRecords;
                                lblStatus.Text = "Inserting... " + argInsertStatus.CurrentRecord + "/" + argInsertStatus.MaxRecords;
                            }
                        }
                        catch (Exception exp)
                        {
                            MessageBox.Show("Error : " + exp.Message);
                        }
                    }
                });
            }
            catch (Exception exp)
            {
                MessageBox.Show("Error : " + exp.Message);
            }
        }

        public void UpdateUI(Object objInsertStatus)
        {
            try
            {
                InsertStatus argInsertStatus = objInsertStatus as InsertStatus;
                if (argInsertStatus.Error != null && argInsertStatus.Error.ToString().Trim().Length > 0)
                {
                    lblStatus.Text = "Error : " + argInsertStatus.Error;
                    prgInsertStatus.Value = 0;
                }
                else if (argInsertStatus.Completed)
                {
                    lblStatus.Text = "Completed";
                    prgInsertStatus.Value = 0;
                }
                else if (argInsertStatus.Cancelled)
                {
                    lblStatus.Text = "Cancelled";
                    prgInsertStatus.Value = 0;
                }
                else
                {
                    prgInsertStatus.Value = argInsertStatus.CurrentRecord;
                    prgInsertStatus.Maximum = argInsertStatus.MaxRecords;
                    lblStatus.Text = "Inserting... " + argInsertStatus.CurrentRecord + "/" + argInsertStatus.MaxRecords;
                }
            }
            catch (Exception exp)
            {
                MessageBox.Show("Error : " + exp.Message);
            }
        }
        private void btnCancel_Click(object sender, EventArgs e)
        {
            try
            {
                //cancelling token source
                if (!cancellationSrc.IsCancellationRequested)
                    cancellationSrc.Cancel();
            }
            catch (Exception exp)
            {
                MessageBox.Show("Error : " + exp.Message);
            }
        }
    }
}


No comments:

Post a Comment