Click here to Skip to main content
15,885,757 members
Articles / Mobile Apps

Mobile Development: Easy to Use Background Thread With GUI Update

Rate me:
Please Sign up or sign in to vote.
4.50/5 (4 votes)
4 Jun 2010CPOL3 min read 17K   10   1
How to update GUI elements from inside a worker thread without using Invoke. Uses MessageWindow, WndProc and SendMessage with WM_COPYDATA.

Although there are well know ways to update the GUI from a background thread not running in the GUI thread, I looked for an easiest to use solution. After some experiments, I got a background thread class that is very easy to use. No BeginInvoke or Control.Invoke needed any more to update a GUI element.

The final solution uses a MessageWindow inside a control based class with a worker thread. As the control and the MessageWindow is part of the GUI thread, there is no need to use Invokes.

Inside the thread, I use SendMessage to transfer background thread information to the control, which then fires an event you can subscribe in the GUI thread.sample.

The test app attached shows three threads running independently and all update the GUI frequently without blocking the GUI.

Image 1

Here is my try to visualize my idea and solution.

Image 2

Normally, you have to use such a construction:

C#
// this variable will hold some text set by the worker thread
public string Message = "";
 
// Create a worker thread and then add items to the ListBox from the
// UI thread
public void DoThreading()
{
    // Create the worker thread and start it
    ThreadStart starter = new ThreadStart(this.UpdateListBox);
    Thread t = new Thread(starter);
    t.Start();
 
    // Loop 4 times, adding a message to the ListBox each time
    for(int i = 0; i < 4; i++);
    {
        this.listBox1.Items.Add("Message from UI thread");
        this.listBox1.Update();
        // Process any queued events on the UI thread
        Application.DoEvents();
        // Suspend processing for 1 second
        Thread.Sleep(1000);
    }
    this.listBox1.Items.Add("Last message from UI thread");
    this.listBox1.Update();
}
 
public void UpdateListBox()
{
    for(int j = 0; j < 5; j++)
    {
        // Set the message to be added to the ListBox from the worker
        // thread
        this.Message = "Worker thread loop count = " + j.ToString();
        // Invoke the WorkerUpdate method in the ListBox’s thread
        // context
        this.listBox1.Invoke(new EventHandler(WorkerUpdate));
        Thread.Sleep(700);
    }
}
// The delegate that’s called from the worker thread
// to update the ListBox
public void WorkerUpdate(object sender, EventArgs e)
{
    this.listBox1.Items.Add(this.Message);
    this.listBox1.Update();
}

As you can see from the code, you need to inform the worker thread about the delegate to use for GUI updates. An GUI update from the worker thread is done with the help of UpdateListBox() and that invokes WorkerUpdate and that function finally updates the GUI. So four places are tight together with function names and definitions.

There are also other ways to do it. For example, with declaring a delegate first and then Invoke from background thread (see here):

C#
delegate void setText(string sText);
...
private void ThreadProcSafe()
		{
			this.SetText("This text was set safely.");
		}
...
		private void SetText(string text)
		{
			// InvokeRequired required compares the thread ID of the
			// calling thread to the thread ID of the creating thread.
			// If these threads are different, it returns true.
			if (this.textBox1.InvokeRequired)
			{
				SetTextCallback d = new SetTextCallback(SetText);
				this.Invoke(d, new object[] { text });
			}
			else
			{
				this.textBox1.Text = text;
			}
		}

But this solution also needs your background thread to know the delegate name (SetText(string s)).

I also used this pattern often, but finally I thought, I need an easy to use class that is independent from the main GUI thread code and easy to re-use.

With my bgThread classes (yes, there are three predefined ones), you can simply use this construction:

C#
    public partial class ThreadTest2 : Form
    {
        private bgThread _bgThread;
...
        //start and link the thread
        private void btn_bgThreadTest_Click(object sender, EventArgs e)
        {
            _bgThread = new bgThread("192.168.128.5");
            _bgThread.bgThreadEvent += new bgThread.bgThreadEventHandler(_bgThread_bgThreadEvent);
        }
 
        void _bgThread_bgThreadEvent(object sender, bgThread.BgThreadEventArgs bte)
        {
            listBox1.Items.Insert(0, "bgThread: " + bte.iStatus.ToString();
        }
...
        //in your exit function you MUST dispose existing threads
            if (_bgThread != null)
                _bgThread.Dispose();
            Application.Exit();
...

As you can see, you have only a bgThread object and after creating a new instance, just add the eventhandler. From the eventhandler, you can directly update the GUI. You don’t have to define a delegate.

All three bgThread classes work fine. They differ in the amount and type of data you can transfer from the background thread to the GUI and so the BgThreadEventArgs differ in their definition.

The first class, bgThread1 class, only uses the IntPtr’s usable with SendMessage to transfer data. So you can transfer a maximum of two integers or so. That is enough for the most usage scenarios, where you only need to know status codes from the background thread. The attached bgThread1 class only impements one DWORD (or int).

The second class, bgThread class, uses a global lock and can transfer the data you desire. You have to redefine only the bgThreadEventArgs class to include the data types you would like to transfer. This class uses PostMessage and NOT SendMessage. It may happen that the data and the message will get out of sync as PostMessage works asynchron.

The third class, bgThread2 class, uses WM_COPYDATA to transfer structured data from the background thread. It is more complicated to use and extend than bgThread1 but uses SendMessage and no lock objects. Your data is delivered with SendMessage and so there will be no problem with data and message syncronization.

All classes define a class based on Component.

C#
    public class bgThread : Component
    {
        public delegate void bgThreadEventHandler(object sender, BgThreadEventArgs bte);
        public event bgThreadEventHandler bgThreadEvent;
...
        internal BgThreadEventArgs _BGeventArgs;
        private Thread myThread;
...
        internal class bgThreadWndProc : MessageWindow
        {
            public bgThreadWndProc(bgThread Parent)
            {
                this._bgThread = Parent;
                hwndControl = Hwnd;
            }
            protected override void WndProc(ref Message m)
            {
                int iMsg = m.Msg;
                System.Diagnostics.Debug.WriteLine("WndProc called...");
                switch (iMsg)
                {
                    case msgID:
                        {
                            this._bgThread.NotifyData(m.WParam);
                            break;
                        }
                    default:
                        {
                            base.WndProc(ref m);
                            break;
                        }
                }
            }
        }//MsgWnd

Within the bgThread class, there is a nested class based on MessageWindow.

The bgThread class constructor:

C#
public bgThread()
{
    bgWnd = new bgThreadWndProc(this);
    myThread = new Thread(myThreadStart);
    bRunThread = true;
    myThread.Start();
}

The thread proc itself. It may contain more or less blocking functions calls.

C#
private void myThreadStart()
 {
     try
     {
         do
         {
             //The blocking function...
             //sample: call ping and return number of pings answered
             int iReply = myPing.Ping(System.Net.IPAddress.Parse(_sIP));
             //create a msg and send it to the messagewindow,
             //the messagewindow will then inform the event subscribers
             //we only need the number of ping replies (0 or 1) for the ping
             Microsoft.WindowsCE.Forms.Message msg =
             Message.Create(bgWnd.Hwnd, msgID, new IntPtr(iReply), IntPtr.Zero);
             MessageWindow.SendMessage(ref msg); //async Message send
             Thread.Sleep(1000); //if you have fast 'blocking' functions you should sleep
         } while (bRunThread);
     }
     catch (ThreadAbortException)
     {
         System.Diagnostics.Debug.WriteLine("Thread will abort");
         bRunThread = false;
     }
     catch (Exception ex)
     {
         System.Diagnostics.Debug.WriteLine("Exception in ThreadStart: " + ex.Message);
     }
     System.Diagnostics.Debug.WriteLine("ThreadProc ended");
 }

The code that fires the event is not called directly from the thread. Instead, it is called from the MessageWindow. The thread itself sends its ‘data’ via SendMessage to the bgThreadComponent’ - to the MessageWindow (which is part of the GUI!):

C#
private void NotifyData(IntPtr i1)
{
    BgThreadEventArgs _bgThreadEventArgs;
    //is there any subscriber
    if (this.bgThreadEvent == null)
    {
        return;
    }
    try
    {
        int i = i1.ToInt32();
        _bgThreadEventArgs = new BgThreadEventArgs(i);
        this.bgThreadEvent(this, _bgThreadEventArgs);
    }
    catch (MissingMethodException)
    {
    }
}

The above code snippets are taken from bgThread1 class, the one that only ‘transfers’ an integer to the GUI.

Download

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
chrwal17-Oct-10 11:26
chrwal17-Oct-10 11:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.