views:

218

answers:

3

I'm having a problem with handling an event on a different thread from where it is raised. The object that is handling the event is not an UI object however, so I can't use Invoke to execute the delegate and automatically switch to the UI thread for event handling.

The situation is as following: I have an MDI application containing multiple forms. Each form has it's own controller class that handles communication between the coupled form and external objects. All forms are either overview or detail forms (e.g. ContactsOverview & ContactDetail) and share the same data.

In the situation where the error occurs the forms appear in a wizard-like sequence, say a detail form is followed by an overview form. In the detail form data used on the following overview form is changed and before switching to the overview form these changes need to be reflected there. An event is raised from the detail form and handled by the controller for the overview form which does the necessary updating of UI elements.

Now the saving of the changed data in the detail form can take a while so it is necessary that the UI remains responsive and other parts of the application can still be used. This is why a backgroundworker is started to handle this. When the data is saved the event is raised on the background thread. The controller for the overview handles this but when the UI needs to be update there are of course cross-thread exceptions.

So what I need is a way to raise the event on the UI thread, but since the handling doesn't happen on a UI element there's no way to switch threads automatically using Invoke.

From searching around the web I've found one possible solution which is using the producer/consumer pattern. But this would require each controller to listen to a queue of events in a separate thread as far as I understand. Since it's an MDI application there could theoretically be any number of forms with controllers and I don't want to be starting up that many threads.

Any suggestions are welcome. If there would be a way to avoid using the backgroundworker alltogether that would be a suitable solution as well.

Thanks for reading,

Kevin

A: 

You can have an event on the background objec that the UI element subscribes to. In the event handler (of the subscription - so it is part of the window code) you can then to the invocation. This is how I solve this.

TomTom
Problem is the UI element isn't subscribing to the event, the controller for the UI element is, which is just a regular c# class and doesn't have the invoke methods.
kwe
Well... in this case I would say that decoupling the controller totally from the UI element does not work, or ;)Maybe the controlelr should get a pointer to the form, so it has something to invoke on...
TomTom
kwe
+1  A: 

You can use SynchronizationContext, specifically SynchronizationContext.Current, to post messages to the main synchronization context (which is the main thread for a GUI application).

Unfortunately I don't know enough about the class and its usage to say this is a definite solution. In particular, I don't know what you should do if you don't require the main thread to handle your events, but instead a particular thread.

Perhaps the WindowsFormsSynchronizationContext class can help you out, it has a public parameterless constructor, I'm thinking it might associate it with the current thread, so if you construct that object from the thread that owns the controller, and give it to the background thread code, it might work.

Lasse V. Karlsen
Thanks, I'll look into it.
kwe
Exactly what I was about to suggest...
Thomas Levesque
After reading this helpful article - http://msmvps.com/blogs/manoj/archive/2005/11/03/74120.aspx - I'm pretty sure this approach will be able to solve my problem. From what I understand you can use SynchronizationContext to hold a reference to the calling thread and after the processing in the background thread is done you can launch the event on the calling thread using it's reference in the SynchronizationContext. I've tested it in a quick demo project and it works. Thanks again!
kwe
A: 

You can try this flag but I don't think it's the best idea, just a work around.

You could also try to instantiate the issuing objects in a non-graphical thread, which may fix your problem.

One more thing, can't you have your UI component handle RunWorkerCompleted (with indirections) ?

Seb
You mean to start the background worker from the UI element and then access the UI thread through the UI element in RunWorkerCompleted?
kwe
Scratch the first comment, I get what you mean now. Problem is the saving of data automatically triggers the raising of the event and separating that part would be quite a lot of work, more than going with the use of SynchronizationContext which I accepted as answer. But it's still a viable solution. Thanks.
kwe