Firing thread safe events in Compact Framework
Changing UI from thread
Before we look at the events, lets have a look at possibilities for changing UI from thread. All UI controls has methods Invoke and/or BeginInvoke. These methods accepts delegates as parameters. Invoking means, that the delegate given as the parameter will be exceuted in thread that is owner of the UI control. This will not cause parallel access to the UI resource, so no exception will be thrown.The first option for invoking uses BeginInvoke method and WaitCallback delegate.
public partial class Form1 : FormThis solution is suitable for most situations where simple status reporting is required. Disadvantage of this solution is the only one argument of WaitCallback delegate. In case of multiple informations to be given, you will need to pass structure or array as the argument. Anyway, I'am still using this solution a lot.
{
Thread thr;
public Form1()
{
InitializeComponent();
thr = new Thread(new ThreadStart(ThreadWorker2));
thr.Start();
}
private void ThreadWorker()
{
int i = 0;
while(true)
{
string message = string.Format("Value of i={0}", i++);
UpdateStatus(message);
Thread.Sleep(1000);
}
}
private void UpdateStatus(object status)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new WaitCallback(UpdateStatus), status);
return;
}
label1.Text = (string)status;
}
}
Second option offers more scalability and more arguments to be passed, because of custom delegate instead of the WaitCallback. Following code is also part of the previous Form1 class.
public delegate void StatusEventHandler(DateTime date, string message);
private void ThreadWorker2()
{
int i = 0;
StatusEventHandler statusHandler = UpdateStatus2;
while (true)
{
string message = string.Format("Value of i={0}",i++);
this.Invoke(statusHandler, new object[] { DateTime.Now, message});
Thread.Sleep(1000);
}
}
private void UpdateStatus2(DateTime date, string message)
{
label1.Text = date.ToShortTimeString();
label1.Text += " " + message;
}
Firing thread safe events
Applications are usually splited into two or three layers, where UI and business logic are different layers. When event comes from business layer to UI layer we need to update some status or information. Problem is, if logic in business layer is running in thread. In that case, we need to catch the event and pass the status into thread safe Invokation described above. Such a design is really lousy, because UI logic must in fact rethrow the event to itself and layers are no more strictly separated.It is necessary to fire event in business layer thread safe way, so that UI layer can immidiately show the status. Since delegates and events in Compact Framework has no Target property as it's in full .NET Framework, we need some workaround. Let's have a look at following class. It is simple worker class representing business layer. Class has UIControl property which represent the UI control that will be Invoked. So FireStatusUpdate method Invokes the control and fires the event. This means, the event will be fired in thread which owns the UIControl.
using System;
using System.Windows.Forms;
using System.Threading;
namespace ThreadTest
{
public class WorkerClass
{
private Thread thr;
// UI control for update
public Control UIControl { get; set; }
public delegate void StatusUpdate(DateTime dateTime, string message);
public event StatusUpdate OnStatusUpdate;
// Starts thread
public void Start()
{
thr = new Thread(new ThreadStart(MainWorker));
thr.Start();
}
// Main thread worker
public void MainWorker()
{
int i = 0;
while (true)
{
string message = string.Format("Value of i={0}", i++);
FireStatusUpdate(DateTime.Now, message);
Thread.Sleep(1000);
}
}
// Fire thread safe event
private void FireStatusUpdate(DateTime dateTime, string message)
{
// UIControl is set and OnStatusUpdate has subscriber
if (UIControl != null && OnStatusUpdate != null)
{
if (UIControl.InvokeRequired)
{
UIControl.Invoke(new StatusUpdate(FireStatusUpdate),
new object[] { dateTime, message });
return;
}
OnStatusUpdate(dateTime, message);
}
}
}
}
Following code is simple UI layer for WorkingClass.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace ThreadTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
WorkerClass worker = new WorkerClass();
// add event handler
worker.OnStatusUpdate += new WorkerClass.StatusUpdate(worker_OnStatusUpdate);
// add UI control to invoke
worker.UIControl = this;
worker.Start();
}
void worker_OnStatusUpdate(DateTime dateTime, string message)
{
label1.Text = dateTime.ToLongTimeString();
label1.Text += " " + message;
}
}
Thread safe events in full framework
Following snippet shows the modification of the FireStatusUpdate method, to be used in full .NET Framework. There is no need for UIControl property, because delegates contains automaticaly filled property Target.// Fire thread safe event
private void FireStatusUpdate(DateTime dateTime, string message)
{
// if OnStatusUpdate has subscriber
if (OnStatusUpdate != null)
{
// List all handlers
foreach (StatusUpdate handler in OnStatusUpdate.GetInvocationList())
{
// if handler target is control then Invoke it
if (handler.Target is Control)
(handler.Target as Control).Invoke(handler, new object[] { dateTime, message });
else
handler(dateTime, message);
}
}
}
No comments:
Post a Comment