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 informations 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.

Here is my try to visualize my idea and solution

Normally you have to use such a construction:
public string Message = "";
public void DoThreading()
{
ThreadStart starter = new ThreadStart(this.UpdateListBox);
Thread t = new Thread(starter);
t.Start();
for(int i = 0; i < 4; i++);
{
this.listBox1.Items.Add("Message from UI thread");
this.listBox1.Update();
Application.DoEvents();
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++)
{
this.Message = "Worker thread loop count = " + j.ToString();
this.listBox1.Invoke(new EventHandler(WorkerUpdate));
Thread.Sleep(700);
}
}
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):
delegate void setText(string sText);
...
private void ThreadProcSafe()
{
this.SetText("This text was set safely.");
}
...
private void SetText(string text)
{
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 patterns 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:
public partial class ThreadTest2 : Form
{
private bgThread _bgThread;
...
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();
}
...
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 there 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.
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;
}
}
}
}
Within the bgThread class there is a nested class based on MessageWindow.
The bgThread class constructor:
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.
private void myThreadStart()
{
try
{
do
{
int iReply = myPing.Ping(System.Net.IPAddress.Parse(_sIP));
Microsoft.WindowsCE.Forms.Message msg = Message.Create(bgWnd.Hwnd, msgID, new IntPtr(iReply), IntPtr.Zero);
MessageWindow.SendMessage(ref msg); Thread.Sleep(1000); } 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 bgThread ‘Component’ - to the MessageWindow (which is part of the GUI!):
private void NotifyData(IntPtr i1)
{
BgThreadEventArgs _bgThreadEventArgs;
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.
Downloads:
DOWNLOAD:bgThread classes, a ping class and a demo application (VS2005, target WM6SDK) - (Hits: 1, size: 20.06 KB)
delegate void SetTextCallback(string text);