Forms, threading, and frustration

I have a fair amount of windows forms code that uses multiple threads. Because of
the way that Windows handles its user interface, you should only be updating the user
interface from the main thread. If you try to do it on other threads, bad things happen,
and they can be pretty hard to track down.

Windows forms includes some code to detect when that is happening, but it can’t do
it in all cases. Well, it could, but if it did, the perf would be pretty atrocious.

When you get in this situation, you need to call Invoke() on the form, and pass it
a delegate to the function that you want to be called on the main thread. In my case,
I need to do an update to my form text when I get an event from the other thread.
My code looks something like this:

                // setup code
            object.RemoteUpdate += new UpdateHandler(RemoteUpdateFunc); 
        public void RemoteUpdateFunc2(object sender, RemoteUpdateEventArgs args) 
            // use the values here. 
        public void RemoteUpdateFunc(object sender, RemoteUpdateEventArgs args) 
            this.Invoke(new UpdateHandler(RemoteUpdateFunc2), new object[]
        {sender, args}); 

I have to create a separate function just to do the forwarding, and I have to do that
for every event that I want to hook to. That’s a lot of boilerplate code that I don’t
want to write.

So, I set out to try to create a class that could wrap the object. Here’s the class
that I wrote:

  	public class Invoker
		Delegate d;
		Form form;

		public Invoker(Form form, Delegate d)
			this.d = d;
			this.form = form;

		public Delegate Handler
				return Delegate.CreateDelegate(d.GetType(), this, "Dispatcher");

		public void Dispatcher(object sender, EventArgs args)
			form.Invoke(d, new object[] { sender, args });

The goal would be to write code like this:

           Invoker invoker = new Invoker(this, new UpdateHandler(RemoteUpdateFunc));
           object.RemoteUpdate += (UpdateHandler) invoker.Handler;

Unfortunately, delegates can only point to methods that are *identical* to the delegate
definition. You can’t, for example use a delegate that’s defined as:

        public delegate void EventHandler(object sender, EventArgs args);

to point to:

        public delegate void UpdateHandler(object sender, RemoteEventArgs args); 

even though RemoteEventArgs is derived from EventArgs. So, that means that you can
only use this approach to point to delegates that have EventArgs as their second parameter,
which doesn’t make it very interesting.

So, I had to abandon this approach. The alternative is to modify the class that I’m
using so that it can make the call. I didn’t want that class to have to have a reference
to the Form class, so I created a delegate like:

        public delegate void InvokeHandler(Delegate d, object[] args);    

and passed that to my the class that has the events. It can then use the delegate
to make the Invoke happen. Not as clean as I had hoped, but it does help a bit.

Comments (2)

  1. Geert Baeyaert says:

    You don’t really need two methods. You can also use the Form.InvokeRequired property, which indicates whether or not you are on the thread the form was created on. InvokeRequired is, for obvious reasons, thread-safe.

    Your code would then look like this:
    // setup code
    object.RemoteUpdate += new UpdateHandler(RemoteUpdateFunc);
    public void RemoteUpdateFunc(object sender, RemoteUpdateEventArgs args)
    if (InvokeRequired) this.Invoke(new UpdateHandler(RemoteUpdateFunc), new object[] {sender, args});
    // use the values here

    Using this construct, if the event is raised on a non-ui thread, the handler will first invoke itself, executing the else branch on the second pass.
    In the other case, the invoke call will be skipped.


  2. Eric says:

    Hmm. That’s an interesting alternative, but it involves a fair amount of extra plumbing in all the handler functions, which I’d like to avoid.