Asynchronous delegates in C#

I wanted to understand delegates and ways of invoking code asynchronously a bit better, so that I could write a better handler for controlling a TCP socket. This is a post to show a small test program to handle a class with a long process in it that will fire an event when the process finishes. It was originally from an example by Silan Liu at http://progtutorials.tripod.com/C_Sharp.htm. I’ve added the class and events to his invoking.

slowcalc1This is the final form. I put the FireEvent on first to check that I could receive back an event that was fired from the class. The Slow Calc button just calls the calculation directly and has to wait for the reply. While waiting, the Incr button is unable to do anything. If the Async Calc button is pressed, the same calculation is done but this time asynchronously. Pressing the Incr button while waiting will add to the number in the bottom text box, showing that the GUI is still live. Nothing too complicated, but it cleared a few things in my mind.

The final thing that I came up against was that when handling the event, we are still on a separate thread, so we need to do the Invoke to set return values in the text boxes.

So, to the code…

Firstly, I created my own value object for the EventArgs; just to get some flexibility if I needed it.

namespace SlowCalcTest
{
    public class MyEventArgs : EventArgs
    {
        public string m_MyArgsData;
        public int m_Total;
    }
}

The main program is below. I shall describe the main sections. The class created is Calculate.cs, I declare a object, calc of that class. The delegate SetTextCallback is used while setting text box values from a non GUI thread.

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;

namespace SlowCalcTest
{
    public partial class frmMain : Form
    {
        // The class that has the slow procedure
        private Calculate calc;

        // I need a delegate to set GUI text from another thread
        delegate void SetTextCallback(string text);

When creating an object of type Calculate, I add the eventHandler to catch any messages coming back.

        /// 
        /// Main entry for the application
        /// 
        public frmMain()
        {
            InitializeComponent();
            calc = new Calculate();
            calc.CalcCompleted += new MyEventHandler(HandleEvent);
        }

The FireEvent button just calls the test event, which replies with a couple of messages. These will be picked up by the event handler and displayed using the HandleEvent routine.

        private void btnEventFire_Click(object sender, EventArgs e)
        {
            calc.testEvent();
        }

        private void HandleEvent(object sender, MyEventArgs e)
        {
 //           txtValueTotal.Text = e.m_MyArgsData;
            MessageBox.Show(e.m_MyArgsData);

            // I can't set the text because I'm not in the GUI thread
            SetText(e.m_MyArgsData);
            SetTotal(e.m_Total.ToString());
        }

The following two routines are just used to invoke themselves if they are on the other thread. This would normally be the case when an Event fires. It would not be processed by the GUI thread and so we need to do the Invoke. I use one routine for each text box.

        private void SetText(string text)
        {
            if (this.txtMessage.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.txtMessage.Text = text;
            }
        }

        private void SetTotal(string text)
        {
            if (this.txtValueTotal.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetTotal);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.txtValueTotal.Text = text;
            }
        }

This routine picks up the values and call the SlowRoutine directly. Because it’s a direct call the routine uses the GUI thread and while waiting, nothing else in the GUI can be used.

        private void btnSlowCalc_Click(object sender, EventArgs e)
        {
            int input1 = Convert.ToInt16("0" + txtValueA.Text);
            int input2 = Convert.ToInt16("0" + txtValueB.Text);
            string strResult = null; // dummy parameter

            int total = calc.Calc(input1, input2, ref strResult);
            txtValueTotal.Text = total.ToString();
            txtMessage.Text = strResult;
        }

This routine calls asynchronously, so the GUI remains active.

        // delegate tests
        private void btnAsyncCalc_Click(object sender, EventArgs e)
        {
            int input1 = Convert.ToInt16("0" + txtValueA.Text);
            int input2 = Convert.ToInt16("0" + txtValueB.Text);
            string strResult = null; // dummy parameter

            calc.CallAsyncCalc(input1, input2, ref strResult);
        }

This is just a utility button to increment a text box value. It is used to show that the GUI is still active.

        private void btnIncr_Click(object sender, EventArgs e)
        {
            int i = Convert.ToInt16("0" + txtValue.Text);
            i++;
            txtValue.Text = i.ToString();
        }

     }
}

The main work is then done in the Calculate class shown below.

MyEventHandler is what the main app will link to with their own event handler. The CalcDelegate is used within the class to define a delegate for the asynchronous call.

namespace SlowCalcTest
{
    // event handler
    public delegate void MyEventHandler(object sender, 
                                                    MyEventArgs e);

    // define the call format for the delegate operation
    public delegate int CalcDelegate(int input1, int input2,
                                            ref string strResult);

    // From Tutorial by Silan Liu, 
    // http://progtutorials.tripod.com/C_Sharp.htm
    // I want to call a separate class to do the work, so had
    // problems with the gui thread and want to use an event 
    // to fire the reply. 
    // Also added the incrementing button to show it works    

    class Calculate
    {
        public Calculate()
        {
        }

        // define an event handler that people can link to
        public event MyEventHandler CalcCompleted;

        // a delegate to use for the async call
        private CalcDelegate mDeleg;

The OnCompletion routine will check to see if any app has linked to this event handler. If they have ( and it could be more than one) then it will raise the CalcCompleted event.

        // calling this invokes the event in the listening app/s
        protected virtual void OnCompletion(MyEventArgs e)
        {
            // check to see if anyone is listening
            if (CalcCompleted != null) CalcCompleted(this, e);
        }

I used this routine to test that the event handling was correct. The call will simple raise an event, do the work (a delay) and then raise another event.

        public void testEvent()
        {
            // my own event value object, to be flexible later
            MyEventArgs s = new MyEventArgs();
            s.m_MyArgsData = "Starting to wait";
            // raise the event
            OnCompletion(s);

            // delay to emulate a long process
            System.Threading.Thread.Sleep
                            (new System.TimeSpan(0, 0, 0, 2, 0));

            s.m_MyArgsData = "The event happened";
            // raise the event
            OnCompletion(s);
            return;
        }

This is just a routine with a few seconds delay built in to emulate a slow process

        /// 
        /// Slow calculate routine
        /// Returns an integer result and a message in the event
        /// 
        public int Calc(int input1, int input2, 
                                            ref string strResult)
        {
            System.Threading.Thread.Sleep
                            (new System.TimeSpan(0, 0, 0, 5, 0));
            strResult = input1 + " + " + input2 + " = " 
                                            + (input1 + input2);
            return input1 + input2;
        }

This is the asynchronous version. It calls the slow routine through a delegate and assigns the callback routine, CalcCallback. It then returns from the call to the main interface.

        /// 
        /// The call for the Async method
        /// 
        public void CallAsyncCalc(int input1, int input2, 
                                                ref string strResult)
        {
            // set up a delegate for the routine that I want to call
            mDeleg = new CalcDelegate(Calc);

            // reference the function to be called when it finishes
            AsyncCallback callback = new AsyncCallback(CalcCallback);

            // call the function asynchronously
            mDeleg.BeginInvoke(input1, input2, ref strResult,
                                            callback, 123456789);
        }

After the call has finished this routine is called to raise the Event. It gets the result of the calculation, fills the value object with the result and then calls OnCompletion to fire the event in the main app.

        private void CalcCallback(IAsyncResult ar)
        {
            string strResult = null;

            int intResult = mDeleg.EndInvoke(ref strResult, ar);

// debug
// int i = (int)ar.AsyncState;
// MessageBox.Show("The ar.AsyncState is " + i); // shows 123456789

            MyEventArgs s = new MyEventArgs();
            s.m_Total = intResult;
            s.m_MyArgsData = 
                string.Format("AsyncState is {0}, result is {1}",
                                   (int)ar.AsyncState, intResult);
            OnCompletion(s);
        }
    }
}

All the code is above, in-line, but if you want to email me I can send a zip file of the project over to you.

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s