Thursday, June 2, 2011

Background Worker Example - Filling dataset in separate thread - Part 2

In my previous article [BackgroundWorker Component Overview in Winforms - Part 1], I described the working of BackgroundWorker component in Winform Application. In this article, I will demonstrate how to use the BackgroundWorker component to run a time-consuming operation on a separate thread. Example fills the dataset in background thread and also shows progress-bar while method is running.

First I created a class which inherits from BackgroundWorker Class which contains following key elements:

  1. An Enum ActionType : which contains Enum of Actions to do in background. i.e. FillCustData, DownloadCustImgs, etc will be used in method OnDoWork
  2. A public variable DoWorkArgs of type Dictionary<String, Object> for passing Arguments in key, value pair to worker method.
  3. A public class ResultData which contains properties like ResultObject, MsgText, PassedArgs etc to pass information from worker thread to UI layer. So UI layer can know what happened during background and take action based on Result.
  4. A protected override void OnDoWork (DoWorkEventArgs e) method - a key method which is executed during in separate thread.
  5. A winform with Button to start process in background and progressbar component to show progress while method is executing in background.

Here is the code for custom APKWorker Class which inherits from BackgroundWorker Class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.ComponentModel;

namespace TemplateApp
{
    public class APKWorker : BackgroundWorker
    {
        private ActionType ActionName { get; set; }
        private DataTable dtResult = null;
        public Dictionary<String, Object> DoWorkArgs = new Dictionary<string, object>();
       
        public APKWorker()
        {
            this.WorkerReportsProgress = true;
            this.WorkerSupportsCancellation = false;
        }

        public APKWorker(ActionType ActionName) : this()
        {
            this.ActionName = ActionName;
        }

        protected override void OnDoWork(DoWorkEventArgs e)
        {
            switch (this.ActionName)
            {
                case ActionType.RegisterUser:
                    //Call to method RegisterUser()
                    break;
                case ActionType.Insert_Usage_Statistic:
                    //Call to method Insert_Usage_Statistic()
                    break;
                case ActionType.FillUsers:

                    int deptid = 0;
                    if (DoWorkArgs.Keys.Contains("deptid"))
                        deptid = (int)DoWorkArgs["deptid"];


                    dtResult = new DataTable();
                    dtResult.Columns.Add("UID", Type.GetType("System.Int32"));
                    dtResult.Columns.Add("UserName", Type.GetType("System.String"));
                    DataRow dr = null;
                    this.ReportProgress(30, "Started");
                    System.Threading.Thread.Sleep(2000);
                    for (int i = 0; i < 5; i++)
                    {
                        dr = dtResult.NewRow();
                        dr["UID"] = i;
                        dr["UserName"] = "Test" + i.ToString();
                        dtResult.Rows.Add(dr);
                    }
                    this.ReportProgress(80, "Data Filled");
                    System.Threading.Thread.Sleep(2000);
                    e.Result = new ResultData { PassedArgs = DoWorkArgs, MsgText = "Data Filled.", MsgType = ResultData.MessageType.Success, ResultObject = dtResult };
                    this.ReportProgress(100, "Completed");
                    break;
            }
            base.OnDoWork(e);
        }

        public enum ActionType
        {
            RegisterUser,
            Insert_Usage_Statistic,
            FillUsers
        }
    }

    public class ResultData
    {
        public Object ResultObject { get; set; }
        public string MsgText { get; set; }
        public MessageType MsgType { get; set; }
        public Dictionary<String, Object> PassedArgs { get; set; }

        public enum MessageType
        {
            Success,
            Failure
        }       
    }

}

Code to write in Winform for Calling background worker class:

        private void btnTest_Click(object sender, EventArgs e)
        {
            this.progressBar1.Minimum = 0;
            this.progressBar1.Maximum = 100;
            APKWorker objWorker = new APKWorker(APKWorker.ActionType.FillUsers);
            objWorker.DoWorkArgs.Add("userid", 10);
            objWorker.DoWorkArgs.Add("deptid", 3);
            objWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(objWorker_RunWorkerCompleted);
            objWorker.ProgressChanged += new ProgressChangedEventHandler(objWorker_ProgressChanged);
            objWorker.RunWorkerAsync();

        //Implementation Example 2:
        //APKWorker objWorker = new APKWorker();
        //objWorker.DoWork += new DoWorkEventHandler(objWorker_DoWork);
        //objWorker.RunWorkerAsync();
        //void objWorker_DoWork(object sender, DoWorkEventArgs e)
        //{
        //    MessageBox.Show("hello");
        //}
        //Above example is useful, if you need to implement some custom logic in DoWork method. Here, first don't pass Actionname from Constructor,
        //then add the 2nd line - objWorker.DoWork += new DoWorkEventHandler(objWorker_DoWork); and write custom logic in objWorker_DoWork
        }       

        void objWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.progressBar1.Value = e.ProgressPercentage;
            this.lblMsg.Text = (string)e.UserState;
        }

        void objWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            ResultData ResultObj = (ResultData)e.Result;
            DataTable dtUser = (DataTable)ResultObj.ResultObject;
            MessageBox.Show(ResultObj.PassedArgs["deptid"].ToString());
            MessageBox.Show(dtUser.Rows[2]["UserName"].ToString());
            MessageBox.Show("Done");
        }


Hope this example will help to all who wants to use Background worker class to run long running methods in separate thread. Feel free to post your comments.

Background Worker Overview with Example in Winforms - Part 1

Recently, while working on Winform Application, we require certain time consuming tasks on Form load to run in separate Thread. We decided to use Background Worker Component in our application. However, there is need that Background Component will be used in multiple forms. So I decided to create a class which inherits from BackgroundWorker Class and implemented certain common functionality over there. I like to share the code and overview of Background worker class.

BackgroundWorker Component Overview:

The BackgroundWorker class allows you to run an operation on a separate, dedicated thread. Time-consuming operations like
  •     Image downloads
  •     Web service invocations
  •     File downloads and uploads (including for peer-to-peer applications)
  •     Complex local computations
  •     Database transactions
  •     Local disk access
can cause your user interface to hang while they are running. When you want a responsive UI and you are faced with long delays associated with such operations, the BackgroundWorker component provides a convenient solution.

The BackgroundWorker component gives you the ability to execute time-consuming operations asynchronously ("in the background"), on a thread different from your application's main UI thread.

To use a BackgroundWorker, you simply tell it what time-consuming worker method to execute in the background, and then you call the RunWorkerAsync method. Your calling thread continues to run normally while the worker method runs asynchronously. When the method is finished, the BackgroundWorker alerts the calling thread by firing the RunWorkerCompleted event, which optionally contains the results of the operation.

The BackgroundWorker component is available from the Toolbox, in the Components tab. To add a BackgroundWorker to your form, drag the BackgroundWorker component onto your form. It appears in the component tray, and its properties appear in the Properties window.

By using a pattern of method calls (RunWorkerAsync, ReportProgress) and event callbacks (DoWork, ProgressChanged, RunWorkerCompleted), BackgroundWorker helps you easily create a background thread and factor out the stuff that should be running on the background thread versus the foreground thread. 

If your background operation requires a parameter, call RunWorkerAsync with your parameter. Inside the DoWork event handler, you can extract the parameter from the DoWorkEventArgs.Argument property. 

Below image shows the flow of working of Background Worker model.


Above is the brief overview of how Background worker class works and what are its key events and methods. In the next article [Background worker example - Filling dataset in separate thread - Part 2], I explained working of Background worker class with code, which fills the Dataset in background and also shows the progress-bar while running the process in background.