Infosys Microsoft Alliance and Solutions blog

« Troubleshooting BizTalk Applications | Main | BizTalk as an ESB »

Asynchronous Programming Model in .NET Framework 2.0 - Part III,,,

In my previous blogs “Asynchronous Programming Model in .NET Framework 2.0 Part I and Part II, I had explained the basics of EBAPM and about various approaches to call an operation in a separate thread. In this final sequel to those blogs, I am going to explain how an operation should be designed so that it would be invoked asynchronously based on EBAPM. It is better to recollect that EBAPM provides more flexibility to an asynchronous operation. EBAPM provides flexibility in canceling an ongoing asynchronous operation. It also supports retrieving incremental result and status from an asynchronous operation. Apart from those, it is possible to invoke the same operation more than once asynchronously if application logic requires that.

 

To explain how to code an operation to provide such flexibilities, I have created a simple application that retrieves the private data such as address, credit card history, employment details, medical history and travel details of a person based on the unique identification of person such as SSN.

However, as this SpyHard application is just to show the capabilities of EBAPM and I am not aware of the central repository wherein I can retrieve all such private data, I have used Thread.Sleep() so liberally in my coding as a substitute to implementing that logicSmile. And it also does not have logic to do the name resolution from supplied SSN so you will get to see the same hard coded data of mine for any SSN even if there is no SSN (So, don’t try to get the private details of your friends and foes through this applicationSmile). So, This SpyHard application is as dumb and clumsy as the hero (my favorite comedian – Leslie Nielsen) of the “SpyHard” movie which is a great spoof on many other adrenalin pumping action movies. But what we are interested in this code, is to know, if our application needs to implement an operation that is time consuming one, how to implement that based on EBAPM, so that client can invoke it asynchronously and get the incremental details about the operation easily. For that purpose, I have developed a bit contrived operation that retrieves private details of a person, in a time consuming fashion. This operation is also contrived to provide increment result such as name, address, credit card details etc and increment status such as the percentage of completed work.

We need following constructs in order to create an asynchronous operation for BackgroundCheck information retrieval.

BackgroundCheckAsync method

This method implements the logic to help invoking it asynchronously. Two things to be noted in this method are the second parameter – taskId and AsyncOperation class. While this method takes SSN as the first parameter, some unique id needs to be passed as another parameter that helps EBAPM to invoke this method multiple times asynchronously from client side.  AsyncOperation class is one that represents and keeping track of life time of each asynchronous operation that is distinguished by the unique id that I mentioned above.  AsyncOperation object for each asynchronous operation should be stored in the HybridDictionary to ensure that this asynchronous operation should not be invoked more than once with the same unique id. AsyncOperation provides two methods Post and PostOperationCompleted methods that are key methods to inform details such as increment result as well as progress and completion of execution of asynchronous operation, to client applications. These two methods will be invoked within an asynchronous operation. The Post method is to inform the client about the intermediate result and progress of the operation. Basically, this Post method triggers an event, to which, clients will be subscribing to, to get the notification. The PostOperationCompleted method is to inform the client about the completion of execution of asynchronous operation, by triggering an event from within asynchronous operation. The events to be triggered by both Post and PostOperationCompleted methods of AsyncOperation are wrapped inside a delegate of type SendOrPostCallback in System.Threading namespace. You should also notice that, the business logic of this method, has been wrapped up in another private method – RetrieveBackgroundCheckInfo that invokes Post and PostOperationCompleted methods appropriately within it. This private method will be invoked asynchronously in BackgroundCheckAsync method through BeginInvoke of a delegate that represents RetrieveBackgroundCheckInfo method.

BackgroundCheckAsyncCancel method

This method helps to cancel the execution of BackgroundCheckAsync method. It does nothing but removing the AsyncOperation object that keeps track of the life time of the asynchronous operation from HybridDictionary collection.

BackgroundCheckCompleted event

Client application will subscribe to this event to get notification on the completion of asynchronous operation. This is an event, which gets triggered by PostOperationCompleted method of AsyncOperation object within the asynchronous method implementation.

BackgroundCheckProgressChanged event

Client application will subscribe to this event to get increments result value and status of ongoing asynchronous operation. This is an event, which gets triggered by Post method of AsyncOperation object within the asynchronous method implementation.

BackgroundCheckCompletedEventArgs class

This class is derived from “AsyncCompletedEventArgs” class of System.ComponentModel namespace. This class represents the result of the BackgroundCheckAsync method.

BackgroundCheckProgressChangedEventArgs class

This class is derived from “”ProgressChangedEventArgs” class of System.ComponentModel namespace. This class represents the percentage of completion of an asynchronous operation.

BackgroundCheckCompletedEventHandler delegate

It provides the method signature of a delegate that needs to be bound with BackgroundCheckCompleted event at client application.

BackgroundCheckProgressChangedEventHandler delegate

It provides the method signature of a delegate that needs to be bound with PrivateDetailProgressChanged event at client application.

I know that it may seem to be bit of too much to be grasped at one take, as these have been explained in bits and pieces. Seeing the complete code that is given below will provide better context and understanding of all above items.

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Threading;
using System.Collections.Specialized;
namespace SpyHardLibrary
{
    public class SpyHard
    {
        private HybridDictionary userStateToLifetime = new HybridDictionary();
        private delegate void WorkerHandler(string ssn, AsyncOperation asyncOp);
        public delegate void BackgroundCheckCompletedEventHandler(object sender, BackgroundCheckCompletedEventArgs e);
        public delegate void BackgroundCheckProgressChangedEventHandler(BackgroundCheckProgressChangedEventArgs e);
        public event BackgroundCheckCompletedEventHandler BackgroundCheckCompleted;
        public event BackgroundCheckProgressChangedEventHandler BackgroundCheckProgressChanged;
        private SendOrPostCallback onProgressReportDelegate;
        private SendOrPostCallback onCompletedDelegate;
        public SpyHard()
        {
            onCompletedDelegate = new SendOrPostCallback(BackgroundCheckFunctionCompleted);
            onProgressReportDelegate = new SendOrPostCallback(BackgroundCheckReportProgress);
        }
        private void BackgroundCheckFunctionCompleted(object state)
        {
            BackgroundCheckCompletedEventArgs e = state as BackgroundCheckCompletedEventArgs;
            OnBackgroundCheckCompleted(e);
        }
        private void BackgroundCheckReportProgress(object state)
        {
            BackgroundCheckProgressChangedEventArgs e = state as BackgroundCheckProgressChangedEventArgs;
            OnBackgroundCheckProgressChanged(e);
        }
        protected void OnBackgroundCheckCompleted(BackgroundCheckCompletedEventArgs e)
        {
            if (BackgroundCheckCompleted != null)
            {
                BackgroundCheckCompleted(this, e);
            }
        }
        protected void OnBackgroundCheckProgressChanged(BackgroundCheckProgressChangedEventArgs e)
        {
            if (BackgroundCheckProgressChanged != null)
            {
                BackgroundCheckProgressChanged(e);
            }
        }
     
        public void BackgroundCheck(string ssn, ref string nameDetails, ref string addressDetails, ref string ccDetails, ref string employmentDetails, ref string healthDetails, ref string travelDetails)
        {
            string strName = string.Empty;
            string strAddress = string.Empty;
            string strCCHistory = string.Empty;
            string strEmploymentHistory = string.Empty;
            string strHealthHistory = string.Empty;
            string strTravelHistory = string.Empty;
            //logic to retrieve Name
            Thread.Sleep(10000);
            strName = "Ganesan Krishnamurthy";
           
            //logic to retrieve Address
            Thread.Sleep(10000);
            strAddress = "Infosys Technologies Limited, Bangalore, India";
          
            //logic to retrieve Credit Card Details
            Thread.Sleep(10000);
            strCCHistory = "Not So Bad!!!";
            //logic to retrieve Employment Details
            Thread.Sleep(10000);
            strEmploymentHistory = "12+ years of IT experience";
            //logic to retrieve Health Details
            Thread.Sleep(10000);
            strHealthHistory = "Robust";
            //logic to retrieve Travel History
            Thread.Sleep(10000);
            strTravelHistory = "Had been to Melbourne, Australia in 2005";
            nameDetails = strName;
            addressDetails = strAddress;
            ccDetails = strCCHistory;
            employmentDetails = strEmploymentHistory;
            healthDetails = strHealthHistory;
            travelDetails = strTravelHistory;
        }
        public void BackgroundCheckAsync(string ssn, object taskId)
        {
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(taskId);
            lock (userStateToLifetime.SyncRoot)
            {
                if (userStateToLifetime.Contains(taskId))
                {
                    throw new Exception("taskId is already existing!!!");
                }
                else
                {
                    userStateToLifetime[taskId] = asyncOp;
                }
            }
            WorkerHandler workerHandler = new WorkerHandler(RetrieveBackgroundCheckInfo);
            workerHandler.BeginInvoke(ssn, asyncOp, null, null);
        }
        public void BackgroundCheckAsyncCancel(object taskId)
        {
            AsyncOperation asyncOp = userStateToLifetime[taskId] as AsyncOperation;
            if (asyncOp != null)
            {
                lock (userStateToLifetime)
                {
                    userStateToLifetime.Remove(taskId);
                }
            }
           
        }
        private void RetrieveBackgroundCheckInfo(string ssn, AsyncOperation asyncOp)
        {
            ProgressChangedEventArgs pceArgs = null;
            string strName = string.Empty;
            string strAddress = string.Empty;
            string strCCHistory = string.Empty;
            string strEmploymentHistory = string.Empty;
            string strHealthHistory = string.Empty;
            string strTravelHistory = string.Empty;
            pceArgs = new BackgroundCheckProgressChangedEventArgs("", 5, asyncOp.UserSuppliedState);
            asyncOp.Post(onProgressReportDelegate, pceArgs);
            //logic to retrieve Name
            Thread.Sleep(10000);
            strName = "Ganesan Krishnamurthy";
            pceArgs = new BackgroundCheckProgressChangedEventArgs(strName, 15, asyncOp.UserSuppliedState);
            asyncOp.Post(onProgressReportDelegate, pceArgs);
            //logic to retrieve Address
            Thread.Sleep(10000);
            strAddress = "Infosys Technologies Limited, Bangalore, India";
            pceArgs = new BackgroundCheckProgressChangedEventArgs(strAddress, 30, asyncOp.UserSuppliedState);
            asyncOp.Post(onProgressReportDelegate, pceArgs);
            //logic to retrieve Credit Card Details
            Thread.Sleep(10000);
            strCCHistory = "Not So Bad!!!";
            pceArgs = new BackgroundCheckProgressChangedEventArgs(strCCHistory, 45, asyncOp.UserSuppliedState);
            asyncOp.Post(onProgressReportDelegate, pceArgs);
            //logic to retrieve Employment Details
            Thread.Sleep(10000);
            strEmploymentHistory = "12+ years of IT experience";
            pceArgs = new BackgroundCheckProgressChangedEventArgs(strEmploymentHistory, 60, asyncOp.UserSuppliedState);
            asyncOp.Post(onProgressReportDelegate, pceArgs);
            //logic to retrieve Health Details
            Thread.Sleep(10000);
            strHealthHistory = "Robust";
            pceArgs = new BackgroundCheckProgressChangedEventArgs(strHealthHistory, 75, asyncOp.UserSuppliedState);
            asyncOp.Post(onProgressReportDelegate, pceArgs);
            //logic to retrieve Travel Details
            Thread.Sleep(10000);
            strTravelHistory = "Had been to Melbourne, Australia in 2005";
            pceArgs = new BackgroundCheckProgressChangedEventArgs(strTravelHistory, 100, asyncOp.UserSuppliedState);
            asyncOp.Post(onProgressReportDelegate, pceArgs);
            Exception error = null;
            bool cancelled = false;
            object userState = asyncOp.UserSuppliedState;
            BackgroundCheckCompletedEventArgs pdceArgs = new BackgroundCheckCompletedEventArgs(strName, strAddress, strCCHistory, strEmploymentHistory, strHealthHistory, strTravelHistory, error, cancelled, userState);
            asyncOp.PostOperationCompleted(onCompletedDelegate, pdceArgs);
        }
    }
    public class BackgroundCheckCompletedEventArgs : AsyncCompletedEventArgs
    {
        private string strNameValue = string.Empty;
        private string strAddressValue = string.Empty;
        private string strCCHistoryValue = string.Empty;
        private string strEmploymentHistoryValue = string.Empty;
        private string strHealthHistoryValue = string.Empty;
        private string strTravelHistoryValue = string.Empty;
        public BackgroundCheckCompletedEventArgs(string strName, string strAddress, string strCCHistory, string strEmploymentHistory, string strHealthHistory, string strTravelHistory, Exception error, bool cancelled, object userState)
            : base(error, cancelled, userState)
        {
            this.strNameValue = strName;
            this.strAddressValue = strAddress;
            this.strCCHistoryValue = strCCHistory;
            this.strEmploymentHistoryValue = strEmploymentHistory;
            this.strHealthHistoryValue = strHealthHistory;
            this.strTravelHistoryValue = strTravelHistory;
        }
        public string NameDetails
        {
            get
            {
                return strNameValue;
            }
        }
        public string AddressDetails
        {
            get
            {
                return strAddressValue;
            }
        }
        public string CCHistoryDetails
        {
            get
            {
                return strCCHistoryValue;
            }
        }
        public string EmploymentHistoryDetails
        {
            get
            {
                return strEmploymentHistoryValue;
            }
        }
        public string HealthHistoryDetails
        {
            get
            {
                return strHealthHistoryValue;
            }
        }
        public string TravelHistoryDetails
        {
            get
            {
                return strTravelHistoryValue;
            }
        }
    }
    public class BackgroundCheckProgressChangedEventArgs : ProgressChangedEventArgs
    {
        private string strDetailsValue = string.Empty;
        public BackgroundCheckProgressChangedEventArgs(string strDetails, int progressPercentage, object userState)
            : base(progressPercentage, userState)
        {
            this.strDetailsValue += strDetails;
        }
        public string DetailsValue
        {
            get
            {
                return strDetailsValue;
            }
        }
    }
   
}

Though it is bit tedious to implement such asynchronous operation supporting classes, outcome of high flexibility because of this model, really outweighs the effort taken to implement this class. To understand this point better, I have implemented the following windows form application that consumes the above class – its asynchronous operation.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using SpyHardLibrary;
namespace SpyHardWinApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void button1_Click(object sender, EventArgs e)
        {
            SpyHard spyHard = new SpyHard();
            spyHard.BackgroundCheckProgressChanged += delegate(BackgroundCheckProgressChangedEventArgs pdpceArgs)
            {
                progressBar1.Value = pdpceArgs.ProgressPercentage;
                if(string.IsNullOrEmpty(textBoxName.Text)){
                    textBoxName.Text = pdpceArgs.DetailsValue;
                }
                else if(string.IsNullOrEmpty(textBoxAddress.Text)){
                    textBoxAddress.Text = pdpceArgs.DetailsValue;
                }
                else if (string.IsNullOrEmpty(textBoxCCHistory.Text))
                {
                    textBoxCCHistory.Text = pdpceArgs.DetailsValue;
                }
                else if (string.IsNullOrEmpty(textBoxEmploymentHistory.Text))
                {
                     textBoxEmploymentHistory.Text = pdpceArgs.DetailsValue;
                }
                else if (string.IsNullOrEmpty(textBoxHealthHistory.Text))
                {
                    textBoxHealthHistory.Text = pdpceArgs.DetailsValue;
                }
                else
                {
                    textBoxTravelHistory.Text = pdpceArgs.DetailsValue;
                }
            };
            spyHard.BackgroundCheckCompleted += delegate(object sender1, BackgroundCheckCompletedEventArgs pdceArgs)
            {
                textBoxStatus.Text = "Operation Completed!!!";
            };
            spyHard.BackgroundCheckAsync(textBoxSSN.Text, "1");
        }
       
    }
}

The UI of the application will look like as given below after the execution of the asynchronous operation BackgroundCheckAsync.

SpyHard Application UI image

I hope this sample code in this and in my previous blogs on Asynchronous Programming model throw enough light on the concepts behind event based asynchronous programming model and various approaches in .NET Framework 2.0 to invoke an operation in a separate thread, for understanding the asynchronous model that is very essential to develop better performance as well as better UI experience applications.

Comments

Hi,

Take a look at my article on CodeProject at the below URL.

http://www.codeproject.com/cs/library/AsynchronousCodeBlocks.asp

I created a C# 2.0 library that provides an easy way of doing async programming.

I'm eager to see comments from you.

Regards,
Aditya.P

Aditya, I will definitely share my comments with you after i go through your document.

Hi.
I googled all day long and trawled entire MSDN to find some good explanation and working example of the EBAP model. Then suddenly I struck gold when I came across this post. Although I had to wade my way thro' the commenting/explanation and the code, at last i could put 2 and 2 together. Your post really helped.

P.S. I have converted this code into VB.NET and if you want I can mail it to you, so you can post that too, to help lesser mortals like me :-)

Hi all,

I would be interested in the VB.NET code because I am not familiar with the higher aspects of C#.

Thanks in advance

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Please key in the two words you see in the box to validate your identity as an authentic user and reduce spam.

Subscribe to this blog's feed

Follow us on

Blogger Profiles

Infosys on Twitter