Ashley
Ashley

Reputation: 2215

Should events raised by controls be on the UI thread?

I know that when you are the caller, calling a method or otherwise manipulating a control, you should invoke the UI thread (or the thread owning that control, anyway) to do so.

However, when getting called back by a control, via one of its events, is it safe to assume that you're being called on the correct thread?

In my experience with the common controls, this is always true, but then perhaps that is just because most events are the result of user interaction, therefore Windows messages being processed by the main message loop, on the UI thread.

Recently I had an issue with one of my own custom controls which invokes an event for a reason other than in response to user interaction, and sometimes does so on a background thread. In one case, an event handler for the event tried to manipulate another control which generated an illegal cross-thread call exception.

I could make the problem go away by checking whether invocation is necessary in my event handler, but I'm interested to know who is actually 'at fault' here.

I can't find any documentation anywhere that states any 'rules' regarding events on controls, or even best practices. Does anyone know of any? Or, in your opinion, should it be the control's responsibility to call subscribers on the right thread or the subscriber's responsibility to check?

Edit: It appears that nobody has ever heard of any documented convention, but generally agree that it's a good idea to call public events on a Control-derived class on the control's owning thread, to avoid surprised for the consumer.

Upvotes: 4

Views: 452

Answers (3)

Hans Passant
Hans Passant

Reputation: 941545

Avoid assuming black magic at work here, you describe sane outcomes. If you raise an event from a worker thread then the event handler is of course going to run on that thread as well. And if you set properties of UI controls in that handler then you are going to be reminded that this is not a legal thing to do.

The exact same mechanism is at work in events raised by controls. They operate from notification messages sent by Windows so that happens on the thread that pumps the message loop. Invariably your main thread in a Winforms or WPF app, unless you are doing something unwise like creating windows on worker threads. So those events get raised on the "right" thread and updating properties of controls isn't a problem.

Events don't hop from one thread to another, not unless you explicitly write the code to do so. Begin/Invoke(), you already know it.

And watch out for ab/using InvokeRequired, it is an anti-pattern. Not knowing up front what thread a particular piece of code runs on is unhealthy. Say, you execute a long-running dbase query on a worker to avoid freezing the UI. So when its results are available, you know that you are going to have to invoke to display the results. No point in using InvokeRequired, other than for its ability to tell you that there's something really wrong. Be sure to throw an exception when it returns false.

Upvotes: 3

Brian Gideon
Brian Gideon

Reputation: 48949

I know that when you are the caller, calling a method or otherwise manipulating a control, you should invoke the UI thread (or the thread owning that control, anyway) to do so.

Mmm...debatable. Of course you should never attempt to access any UI element from a thread other than the one hosting it. However, using the marshaling techniques like Invoke are not always the best mechanisms for updating UI elements espcially when all you want to do is update the UI with progress information from the worker thread. Do not get me wrong. There is a time and a place where using marshaling operations makes perfect sense, but many times having the UI thread poll for the data it needs to update itself via a System.Windows.Forms.Timer makes for a solution that is usually simpler, more efficient, and just as elegant (if not more so).

However, when getting called back by a control, via one of its events, is it safe to assume that you're being called on the correct thread?

Not necessarily. I mean usually that is the case especially with Control instances. But remember, you can also drop Component instances on your form as well. Many of those raise events on other threads. Consider BackgroundWorker.DoWork and SerialPort.DataReceived as satisfactory counter examples.

I could make the problem go away by checking whether invocation is necessary in my event handler, but I'm interested to know who is actually 'at fault' here.

I am going to have to say the fault lies with you. If your control really does subclass Control than I would try really hard to make sure all events are raised on the UI thread. If you do not then it will surely confuse other developers who (right or wrong) assume that they are on the UI thread. If your "control" only subclasses Component then you are probably okay, but make sure you document the behavior of the component.

Upvotes: 3

David W
David W

Reputation: 10184

Seems to me that if I were a consumer of your control, and using it started throwing thread exceptions, I'd probably be a little irritated :) I think I'd be in the camp of checking in the event being raised, not depending on the subscriber.

Upvotes: 0

Related Questions