Wednesday, June 13, 2012

Asynchronous callback using Delegates in C#


In this post Asynchronous call using Delegates in C#, we shall see on how to use delegates as reference to functions and make a Asynchronous call to the references function.

Delegates in c# act as function pointers to functions, they can refer to any function which has the same signature as the delegate. To know more about the basics of Delegates refer to the post Delegates in C#.

Here we shall see on how Delegates can be used to make Asynchronous calls to the references function, when we make a Asynchronous call the calling thread does not wait for the referenced function to complete its execution, it just makes the call and forgets it, the referenced function completes its execution and notifies the caller by calling the Callback function.

Let us try to use Delegates to populate a DataGridView with data, the Delegate will be mapped to a function in the Domain class and a Asynchronous call is made to the mapped function to populate the DataGridView.

First let us create a Domain layer class clsEmployee, and add a method GetEmployeeList  
to this class, our delegate will be mapped to this method to get the details.

class clsEmployee
{
    public DataTable GetEmployeeList(int DepartmentID)
    {
        // Read the connection string from the app.config file.
        string strConn = ConfigurationSettings.AppSettings["ConnectionString"].ToString();
        SqlConnection objConn = new SqlConnection(strConn);
        SqlCommand objCmd = new SqlCommand("SELECT * FROM EMPLOYEE WHERE DepartmentID = " + DepartmentID, objConn);
        SqlDataAdapter objDA = new SqlDataAdapter(objCmd);
        DataSet dsEmployee = new DataSet();
        //
        objDA.Fill(dsEmployee, "dtEmployee");
        //
        System.Threading.Thread.Sleep(5000);
        return dsEmployee.Tables["dtEmployee"];
    }
}

 Fine, now we will move to the From End code, declare the delegate and map it to the above method.

Declare the Delegate
delegate DataTable GetEmployeeListDelegate(int intDepartmentID);


Initialize the delegate and map it to the function in the Domain class.
clsEmployee objEmployee = new clsEmployee();
delegate_GetEmployees = new GetEmployeeListDelegate(objEmployee.GetEmployeeList);
delegate_GetEmployees.BeginInvoke(Convert.ToInt16(cmbDepartment.SelectedValue), LoadEmployeeCallBack, null);

Here the Delegate in initialized and mapped to the function in the line

delegate_GetEmployees = new GetEmployeeListDelegate(objEmployee.GetEmployeeList);

The call to the mapped function is made indirectly by invoking the delegate, and a reference to the Callback function is passed in the line

delegate_GetEmployees.BeginInvoke(Convert.ToInt16(cmbDepartment.SelectedValue), LoadEmployeeCallBack, null);

When this like gets executed, the mapped function is called through the delegate and a reference to the call back function is passed.

Remember we are making an Asynchronous call with the delegate hence, the User Interface will be not get locked, Once the call is made the UI is free for the user to perform other activities, the mapped function will complete the execution and call the callback function to populate the GridView

private void LoadEmployeeCallBack(IAsyncResult ar)
{
    DataTable dtEmployees;
    dtEmployees = delegate_GetEmployees.EndInvoke(ar);
    //
    loadGridView(dtEmployees);       
}

Here if you notice we are not binding the result DataTable to the GridView in the callback function, this cannot be done since this function is executing in a different thread, trying to bind the datatable dtEmployees to the GridView gvEmployees here will throw the following exception.

Cross-thread operation not valid: Control 'gvEmployees' accessed from a thread other than the thread it was created on.

Hence we need to help of another delegate to populate the Grid with the data.

delegate void loadGridViewDelegate(DataTable dtEmployee);

private void loadGridView(DataTable dtEmployees)
{
    if (gvEmployees.InvokeRequired)
    {
        loadGridViewDelegate del = new loadGridViewDelegate(loadGridView);
        gvEmployees.Invoke(del, new object[] { dtEmployees });
    }
    else
    {
        gvEmployees.DataSource = dtEmployees;
    }
}

The Complete code is as follows

Domain Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;

namespace WinApp
{
    class clsEmployee
    {
        public DataTable GetEmployeeList(int DepartmentID)
        {
            // Read the connection string from the app.config file.
            string strConn = ConfigurationSettings.AppSettings["ConnectionString"].ToString();
            SqlConnection objConn = new SqlConnection(strConn);
            SqlCommand objCmd = new SqlCommand("SELECT * FROM EMPLOYEE WHERE DepartmentID = " + DepartmentID, objConn);
            SqlDataAdapter objDA = new SqlDataAdapter(objCmd);
            DataSet dsEmployee = new DataSet();
            //
            objDA.Fill(dsEmployee, "dtEmployee");
            //
            System.Threading.Thread.Sleep(5000);
            return dsEmployee.Tables["dtEmployee"];
        }
    }
}

UI Windows Form code
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.Configuration;
using System.Data.SqlClient;

namespace WinApp
{
    delegate DataTable GetEmployeeListDelegate(int intDepartmentID);
    delegate void loadGridViewDelegate(DataTable dtEmployee);
    //
    public partial class frmViewEmployees : Form
    {
        GetEmployeeListDelegate delegate_GetEmployees;
        //
        private void btnLoadEmployees_Click(object sender, EventArgs e)
        {
            clsEmployee objEmployee = new clsEmployee();
            delegate_GetEmployees = new GetEmployeeListDelegate(objEmployee.GetEmployeeList);
            delegate_GetEmployees.BeginInvoke(Convert.ToInt16(cmbDepartment.SelectedValue), LoadEmployeeCallBack, null);
        }
        //
        private void LoadEmployeeCallBack(IAsyncResult ar)
        {
            DataTable dtEmployees;
            dtEmployees = delegate_GetEmployees.EndInvoke(ar);
            //
            gvEmployees.DataSource = dtEmployees;
            loadGridView(dtEmployees);       
        }
        //
        private void loadGridView(DataTable dtEmployees)
        {
            if (gvEmployees.InvokeRequired)
            {
                loadGridViewDelegate del = new loadGridViewDelegate(loadGridView);
                gvEmployees.Invoke(del, new object[] { dtEmployees });
            }
            else
            {
                gvEmployees.DataSource = dtEmployees;
            }
        }
        //
        public frmViewEmployees()
        {
            InitializeComponent();
        }
        //

        private void frmViewEmployees_Load(object sender, EventArgs e)
        {
            BindDepartments();
        }
        //
        private void BindDepartments()
        {
            // Read the connection string from the app.config file.
            string strConn = ConfigurationSettings.AppSettings["ConnectionString"].ToString();
            SqlConnection objConn = new SqlConnection(strConn);
            SqlCommand objCmd = new SqlCommand("SELECT 0 as ID,'-Select-' as Name UNION SELECT ID, NAME FROM DEPARTMENT", objConn);
            SqlDataAdapter objDA = new SqlDataAdapter(objCmd);
            DataSet dsDepartment = new DataSet();
            //
            objDA.Fill(dsDepartment, "dtDepartment");
            //
            cmbDepartment.DataSource = dsDepartment.Tables["dtDepartment"];
            cmbDepartment.DisplayMember = "Name";
            cmbDepartment.ValueMember = "ID";
        }
    }
}


Search Flipkart Products:
Flipkart.com