IanG on Tap

Ian Griffiths in Weblog Form (RSS 2.0)

Blog Navigation

April (2018)

(1 item)

August (2014)

(1 item)

July (2014)

(5 items)

April (2014)

(1 item)

March (2014)

(1 item)

January (2014)

(2 items)

November (2013)

(2 items)

July (2013)

(4 items)

April (2013)

(1 item)

February (2013)

(6 items)

September (2011)

(2 items)

November (2010)

(4 items)

September (2010)

(1 item)

August (2010)

(4 items)

July (2010)

(2 items)

September (2009)

(1 item)

June (2009)

(1 item)

April (2009)

(1 item)

November (2008)

(1 item)

October (2008)

(1 item)

September (2008)

(1 item)

July (2008)

(1 item)

June (2008)

(1 item)

May (2008)

(2 items)

April (2008)

(2 items)

March (2008)

(5 items)

January (2008)

(3 items)

December (2007)

(1 item)

November (2007)

(1 item)

October (2007)

(1 item)

September (2007)

(3 items)

August (2007)

(1 item)

July (2007)

(1 item)

June (2007)

(2 items)

May (2007)

(8 items)

April (2007)

(2 items)

March (2007)

(7 items)

February (2007)

(2 items)

January (2007)

(2 items)

November (2006)

(1 item)

October (2006)

(2 items)

September (2006)

(1 item)

June (2006)

(2 items)

May (2006)

(4 items)

April (2006)

(1 item)

March (2006)

(5 items)

January (2006)

(1 item)

December (2005)

(3 items)

November (2005)

(2 items)

October (2005)

(2 items)

September (2005)

(8 items)

August (2005)

(7 items)

June (2005)

(3 items)

May (2005)

(7 items)

April (2005)

(6 items)

March (2005)

(1 item)

February (2005)

(2 items)

January (2005)

(5 items)

December (2004)

(5 items)

November (2004)

(7 items)

October (2004)

(3 items)

September (2004)

(7 items)

August (2004)

(16 items)

July (2004)

(10 items)

June (2004)

(27 items)

May (2004)

(15 items)

April (2004)

(15 items)

March (2004)

(13 items)

February (2004)

(16 items)

January (2004)

(15 items)

Blog Home

RSS 2.0

Writing

Programming C# 5.0

Programming WPF

Other Sites

Interact Software

Windows Forms Ate My EventArgs

Thursday 2 September, 2004, 11:00 AM

There's an issue with Control.BeginInvoke and Control.Invoke that comes up from time to time. As it happens, two people have asked me about it in the last two days, so I thought I'd blog about it. Next time I can point to this blog. (Or, more likely, nobody will ask me about it for ages now I've written this. Then I'll forget that I wrote it... So, if you found this via Google, welcome!)

(Control.BeginInvoke is essential in multithreaded Windows Forms applications. It's the only way to pass control from a worker thread back to the UI thread, and since Windows Forms controls have thread affinity, you must do this if you want to update the UI after performing some work on a background thread. If you're not familiar with this technique, see this for more details.)

Control.BeginInvoke (and its evil cousin, Control.Invoke) have some surprising behaviour if you use the System.EventHandler delegate type. EventHandler is used all over the place. Handler functions using it have this kind of signature:

void SomeHandler(object sender, EventArgs e)

The general idea is that the first parameter always refers to the event source, and the second parameter is a placeholder. If can be used to pass in extra information about the event if necessary. If there's nothing more to say beyond the fact that the event just occurred, we usually just pass EventArgs.Empty.

If you wanted to pass extra information, you'd derive a class, something like this:

public class CustomEventArgs : EventArgs
{
    public CustomEventArgs(int val, string msg)
    {
        Value = val;
        Message = msg;
    }
    public readonly int Value;
    public readonly string Message;
}

The theory is you could then write code like this to pass information from a worker thread back to a handler function that will run on the UI thread like so:

CustomEventArgs e = new CustomEventArgs(42, "The answer is...");
object[] args = { this, e };
EventHandler dlg = new EventHandler(RunsOnUIThread);
BeginInvoke(dlg, args);

Then we can write the function that will run on the UI thread and upate the UI with this data passed in from the worker thread:

private void RunsOnUIThread(object sender, EventArgs e)
{
    // This cast will fail
    CustomEventArgs ce = (CustomEventArgs) e;
    label1.Text = ce.Message + ce.Value;
}

But as the comment says, this doesn't work. You get an InvalidCastException. On closer inspection this turns out to be because the e parameter is in fact EventArgs.Empty.

Why? That's not what we asked to get passed in. We carefully built an array of arguments, with a CustomEventArgs as the second argument. Windows Forms has simply ignored this and passed in EventArgs.Empty. And it turns out to be ignoring the first parameter too - that will always be the Control on which you called Invoke, regardless of what you put in the array.

What The?..

For ages I though this was a bizarre bug, but no - it's deliberate. Section 5 of this explains what's going on.

That article describes this behaviour by saying "This is pretty sweet." That's not quite how I characterised it when I first discovered it. "This is pretty darned annoying," is the cleaned-up version of what I said.

I do understand that there is a situation in which this can be useful. If you happen to want the values that Windows Forms supplies for you, it simplifies the code required to call back onto the UI thread. You can just write this:

EventHandler dlg = new EventHandler(RunsOnUIThread);
BeginInvoke(dlg);

And I also understand the benefit of how it avoids the expense of dynamic delegate invocation here. But I don't see any good reason for it to ignore my argument list when I've actually gone to the effort of creating one! Surely in the special case code they added for EventHandler they could check to see if you happen to have passed in a list of parameters, and if you have, pass your ones rather than the default ones. That would still offer the performance benefit, and would let developers choose whether to use the default arguments or pass in their own.

The Workaround

How can you pass in your own arguments then? Just declare your own delegate type. Personally, I'd declare one that specified the exact parameters required. For example, my function requires an integer and a string, so I'd do this:

private delegate void MyHandler(int value, string message);

We're not doing actual event handling here - we're just calling across threads. So there's no particular reason to stick to the EventHandler conventions in this case. But if you happen to like those, I'd still make it strongly typed, e.g.:

private delegate void CustomEventHandler(object sender, CustomEventArgs e);

This gets rid of the need for the cast in the target function. But some people like the cast, apparently... And I suppose it does give you the flexibility to pass in whatever you like, if only Windows Forms would let you. And if that's what you want, just declare your own delegate type that happens to look identical to EventHandler:

private delegate void MyEventHandler(object sender, EventArgs e);

Windows Forms won't use special treatment for this - it keys off type, not signature. So it'll just use it's normal generic code path if you use this delegate type, meaning that it'll pass in the arguments you tell it to, rather than the arguments it thinks you should have passed.

Copyright © 2002-2024, Interact Software Ltd. Content by Ian Griffiths. Please direct all Web site inquiries to webmaster@interact-sw.co.uk