(1 item) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(6 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(4 items) |
|
(2 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(5 items) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(8 items) |
|
(2 items) |
|
(7 items) |
|
(2 items) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(3 items) |
|
(2 items) |
|
(2 items) |
|
(8 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(6 items) |
|
(1 item) |
|
(2 items) |
|
(5 items) |
|
(5 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(16 items) |
|
(10 items) |
|
(27 items) |
|
(15 items) |
|
(15 items) |
|
(13 items) |
|
(16 items) |
|
(15 items) |
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.
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.
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.