Marshalling an Event to the UI Thread

  • Thread starter Thread starter Alphamacaroon
  • Start date Start date
A

Alphamacaroon

All,

I have a quick question regarding events and UI threads. I'm developing
an API (simple C# class with no UI) which I want to be able to give to
other developers to develop their own UI's around. This API has a
number of events which UI developers will want to hook into to be able
to update their UI accordingly. However, many of these events will be
thrown from multiple threads spawned from within the API. My questions
are:

1) Since the events are thrown from threads within the API, the event
handler in the UI application is occuring on the API thread and not the
UI thread right? If this is right, then I know that this is a no-no and
the UI developer will have to call Form.BeginInvoke to marshall any
code back to the UI thread.

2) (Assuming I'm right above) Is there any way to find the UI thread
and marshall the event into it before the event is thrown so that the
UI developer doesn't have to worry about, or remember to use
Begin.Invoke?

At the end of the day I don't want to assume that my UI developers are
going to remember to call Form.BeginInvoke in these events.

Any help would be greatly appreciated!
 
Alphamacaroon said:
1) Since the events are thrown from threads within the API, the event
handler in the UI application is occuring on the API thread and not the
UI thread right?
Yes.

If this is right, then I know that this is a no-no and
the UI developer will have to call Form.BeginInvoke to marshall any
code back to the UI thread.

Yes, but there's more to it than just that. See below.
2) (Assuming I'm right above) Is there any way to find the UI thread
and marshall the event into it before the event is thrown so that the
UI developer doesn't have to worry about, or remember to use
Begin.Invoke?

Well, you could pass a reference to an instance of Form that was created on
the GUI thread and then, within your API object, call Form.BeginInvoke on
that instance.
At the end of the day I don't want to assume that my UI developers are
going to remember to call Form.BeginInvoke in these events.

Well, there's more to it than that.

Say that you have a worker object, Worker, which does some work in a
separate thread and raises events every so often. And you have a form
which handles Worker's events by updating the GUI.

Now, say that the user closes the form and after that the worker raises an
event. You'll get an ObjectDisposedException when trying to update the
GUI.

That's easy to work around but there's a second problem: what if messages
posted via Form.BeginInvoke don't get processed until *after* your form has
been disposed? You'll get an ObjectDisposedException again.

I believe that this can be solved with the following.

class MyForm : Form
{
Worker worker;

void worker_GotData(...) {
if (InvokeRequired) {
// note: must call BeginInvoke - not Invoke - here, or else we
// can deadlock
BeginInvoke(new FooDelegate(worker_GotData));
return;
}
UpdateGui(...);
}

// ...

void Form_Closing(...) {
worker.Stop(); // Worker.Stop must be implemented so that
// the calling thread is blocked until
// it's guaranteed that no more events
// will be raised from Worker

Application.DoEvents(); // process any messages which were posted
// before we called Worker.Stop
}
}

So basically we need to ensure, in a thread-safe manner, that no more
events will be raised from our Worker instance, and then pump the message
queue in order to process any Worker-related messages before our form is
disposed.
 
Doesn't the event delegate get set to null when the form exits? Can't
we just check to see if the event delegate is not null before we raise
the event from within the worker thread? Anyway, I did a bit more
research and I think I found a way to solve my problem. Posting it here
for feedback and to help others. Here's what I did:

I created a function within my worker class which looks like:

private void RaiseEvent(Delegate eventDelegate, object[] args)
{
if(eventDelegate != null)
{
try
{
if(((Control)eventDelegate.Target).InvokeRequired)
{

((Control)eventDelegate.Target).BeginInvoke(eventDelegate, args);
return;
}
}
catch
{
}

eventDelegate.DynamicInvoke(args);
}
}

Whenever I need to raise an event within my worker thread I call this
function with the event delegate as the first parameter and the method
arguments as the second. It first checks to see if the event delegate
has been hooked. Then it sees if the target (which is the object which
hooked the event) can be type cast as a control. If it can, then it
checks InvokeRequired and does a BeginInvoke. If it can't be cast to a
control then it does a regular DynamicInvoke.

Seems to work pretty well so far, but I'm looking for any feedback if
anyone has any.
 
Back
Top