So, it’s been a freakishly long time since my last post here.
I’ve been trying to get better… I even wrote out a list of topics I wanted to write about. So, let’s start talking about them.
As we do more and more work in our applications asynchronously, we’re confronted with the problem of how to communicate the status of the background thread to our users. Firing an event seems like the best way, except anything the user can see has to be done by the UI thread, which is specifically not the thread the background task is running on.
WinForms offers a means of redirecting execution onto the UI thread, the
Form.Invoke
method, but using it makes life difficult. This is particular true when you throw lambdas into the mix. Lambdas made it quite easy to write a simple event handler:
backgroundAction.StepCompleted +=
(s, ea) => {statusMsg.Text = String.Format("Step #{0} Completed", ea.StepNum);}
But when we add is thread marshaling, it gets messy:
backgroundAction.StepCompleted +=
(s, ea) =>{this.Invoke((s1,ea1)=>{statusMsg.Text = String.Format("Step #{0} Completed", ea.StepNum);}, s,ea);
And that’s with always marshaling it. If the event could be fired from both a background thread or from the main thread depending on the context, then you should check the
InvokeRequired
property, if call the action directly if it’s not needed. But complicates our lives,
making us split the action out into a named function:
backgroundTask.StepCompleted += ((s, ea) =>
{
if (this.InvokeRequired)
this.Invoke(UpdateStatus, s, ea);
else
UpdateStatus(s,ea);
});
void UpdateStatus(object sender, MessageEventArgs ea)
{
statusMsg.Text = String.Format("Step #{0} Completed", ea.StepNo);
}
Yech! We have a lambda, but it we’re using it for the boiler-plate code and that we will probably have to repeat. And the real method we want
to perform is in a separate function.
What we need is a handle utility function which will take a method reference, or, better yet, a lambda, and package
it up as we need it. Then we could write it like:
backgroundTask.StepCompleted +=
ToUIThread<StepEventArgs>((s, ea) => statusMsg.Text = String.Format("Step #{0} Completed", ea.StepNo));
The tricky part about this is that it must take a function as a parameter, and return a function. Plus, the compiler can’t figure out the type of the second parameter by itself, so we have to give it some help. And, we we’ll need a non-generic version, for event which are defined as
EventHandle
instead of EventHandler<TEventArgs>
. Make it an extension method on Form, and we’ve got:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
public static class FormExt
{
static public EventHandler<TEventArgs> ToUIThread<TEventArgs>(this Form frm,
EventHandler<TEventArgs> handler) where TEventArgs : System.EventArgs
{
return ((sender, e) =>
{
if (frm.InvokeRequired)
frm.Invoke(handler, sender, e);
else
handler(sender, e);
});
}
static public EventHandler ToUIThread(this Form frm, EventHandler handler)
{
return ((sender, e) =>
{
if (frm.InvokeRequired)
frm.Invoke(handler, sender, e);
else
handler(sender, e);
});
}
}
20+ years as a developer : Assembly, C, C++ and C# (in that order) with sidelines in ASP/VBScript, ASP.Net, JavaScript, Perl, QuickBasic, VisualBasic, plus a few others which I'm not going to mention because if I did someone might ask me to use them again (shudder)
Microsoft MVP in VC++ (1994-2004)
I also run www.NJTheater.com as a hobby.
Full resume & stuff at NovelTheory.com
Underused blog at HonestIllusion.com