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.
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 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:
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 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
.
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.
Download