Sunday, September 18, 2011

Handling Cross Thread operations in Windows Forms




There are many scenerios in day to day development activities and if you are working with thread you always encounter a problem for cross thread operation.In other words you are not able to update properties of a control from some other thread . Here I have tried to depict a simple scenario of updating a progressbar as a problem.


The Code written below show a problem of cross thread operation.
In the code below I have a created a separate thread and implemented a event mechanism to update the progress bar value from a thread that doesnot own the progressbar.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;
using System.Threading;

namespace CrossThreadOperation
{
    public partial class Form1 : Form
    {

        /// <summary>
        /// delegate for callback implementation
        /// </summary>
        /// <param name="value">data from the calling thread</param>
        public delegate void CompletedDelegate(int value);

        /// <summary>
        /// event used for notification by the thread
        /// </summary>
        public event CompletedDelegate Completed;
        public Form1()
        {
            InitializeComponent();
          
            // hooking the callback function
            this.Completed += new CompletedDelegate(Form1_Completed);
        }

        void Form1_Completed(int value)
        {
            progressBar1.Value = value ;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread workerThread = new Thread(DoSomeWork);
            workerThread.Start(); 
        }

        private void DoSomeWork()
        {
            ArrayList arr = new ArrayList();

            for (int index = 65; index < 95; index++)
            {
                arr.Add(index);
            }

            Completed(100);

        }
    }
}

When I try to run this code in Debug mode I get an exception as shown below





 To overcome the problem we have to follow the steps mentioned below
1.   Create a delegate with the same signature as the function that updates a control
2.   Create a function that updates the control.
3.   Instantiated the delegate with the function which updates the control
4.   Check whether the control is updated by the main thread or a separate thread by checking the Invoke Required property of the control. Here the separate thread requests the main thread to update the property of the control.
5.   If the control is being updated from a separate thread then update the control from the delegate
6.   If the control is being updated directly from the main thread then directly update the property of the control.


        /// <summary>
        /// delegate for setting the progressBar
        /// </summary>
        /// <param name="value"></param>
        public delegate void SetProgessBarDelegate(int value);

    
        void Form1_Completed(int value)
        {

          //  progressBar1.Value = value ;

            UpdateProgress(value );

        }

      
        private void DoSomeWork()
        {
            ArrayList arr = new ArrayList();

            for (int index = 65; index < 95; index++)
            {
                arr.Add(index);
            }

            Completed(100);

        }


        /// <summary>
        /// function to avoid the exception due to Cross-Thread Operation
        /// </summary>
        /// <param name="value"></param>
        public void UpdateProgress(int value)
        {
            //intantiate the delegate for thread safe action of
            // of updating the progressbar
            SetProgessBarDelegate setProgess = new SetProgessBarDelegate(UpdateProgress);
          
             //if the value is not updated from the main Thread.
            if (progressBar1.InvokeRequired)
            {
                //you can check the thread id using a message box here to verify it yourself
                //here the worker thread requests the main thread
                // to increment the progress bar
                progressBar1.Invoke(setProgess , new object[] { value });

            }
            else
            {
                //The main thread increments the progressbar
                progressBar1.Value =value ;
            }
        }

            }
        }
   


Finally we observe that the progress bar increments smoothly