views:

5321

answers:

10

I find that the .NET event model is such that I'll often be raising an event on one thread and listening for it on another thread. I was wondering what the cleanest way to marshal an event from a background thread onto my UI thread is.

Based on the community suggestions, I've used this:

// earlier in the code
mCoolObject.CoolEvent+= 
           new CoolObjectEventHandler(mCoolObject_CoolEvent);
// then
private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    if (InvokeRequired)
    {
        CoolObjectEventHandler cb =
            new CoolObjectEventHandler(
                mCoolObject_CoolEvent);
        Invoke(cb, new object[] { sender, args });
        return;
    }
    // do the dirty work of my method here
}
+2  A: 

I shun redundant delegate declarations.

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    if (InvokeRequired)
    {
        Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent, sender, args));
        return;
    }
    // do the dirty work of my method here
}

For non-events, you can use the System.Windows.Forms.MethodInvoker delegate or System.Action.

EDIT: Additionally, every event has a corresponding EventHandler delegate so there's no need at all to redeclare one.

Konrad Rudolph
+2  A: 

From my researching, I found that's the best way to do cross-thread calls.

What I like to do though is set up some generic events/handlers so I can re-use them and reduce code. For example, setting the text of a label is something I do a lot on cross thread calls, so I create a method/delegate for setting text for controls

delegate void SetTextCallback(Control ctrl, string text);

private void SetText(Control ctrl, string text)
{
    if (ctrl.InvokeRequired)
    {
        ctrl.BeginInvoke(new SetTextCallback(SetText), ctrl, text));
    }
    else
    {
        ctrl.Text = text;
    }
}

As for use, I just call SetText() and send in my control and the text I want for it. This lets me re-use my delegates fairly simply. I do the same thing for other cross thread actions setting color, visible, enabled, etc.

This is fairly specific to Windows forms though it can be applied to other situations if neccessary.

Dan Herbert
A: 

I've always wondered how costly it is to always assume that invoke is required...

private void OnCoolEvent(CoolObjectEventArgs e)
{
  BeginInvoke((o,e) => /*do work here*/,this, e);
}
Will
+6  A: 

A couple of observations:

  • Don't create simple delegates explicitly in code like that unless you're pre-2.0 so you could use:

    BeginInvoke(new EventHandler(mCoolObject_CoolEvent), sender, args);

Also you don't need to create and populate the object array because the args parameter is a "params" type so you can just pass in the list.

  • Secondly, I would probably favour Invoke over BeginInvoke as the latter will result in the code being called asynchronously which may or may not be what you're after but would make handling subsequent exceptions difficult to propagate without a call to EndInvoke. What would happen is that your app will end up getting a TargetInvocationException instead.
Shaun Austin
A: 

You can try to develop some sort of a generic component that accepts a SynchronizationContext as input and uses it to invoke the events.

On Freund
A: 

As an interesting side note, WPF's binding handles marshaling automatically so you can bind the UI to object properties that are modified on background threads without having to do anything special. This has proven to be a great timesaver for me.

In XAML:

<TextBox Text="{Binding Path=Name}"/>
gbc
A: 

See this question for a "generic" way to do this.

Benjol
+8  A: 

Oh man, I just put my code for this on the internet today. (Specifically, the #region named "Invoke/BeginInvoke methods using delegates".) And it's so much better than the other suggestions, definitely check it out.

Sample usage:

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    // You could use "() =>" in place of "delegate"; it's a style choice.
    this.Invoke(delegate
    {
        // Do the dirty work of my method here.
    });
}
Domenic
+1 for demonstrating non-LINQ usefullness of extension methods.
galaktor
Excellent stuff.
Joe
+2  A: 

Nick,

Keep in mind that InvokeRequired might return false when an existing managed Control does not yet have an unmanaged handle. You ought to exercise caution in events that will be raised before control has been fully created.

GregC
+1  A: 

I think the cleanest way is definitely to go the AOP route. Make a few aspects, add the necessary attributes, and you never have to check thread affinity again.

Dmitri Nesteruk
I don't understand your suggestion. C# is not a natively aspect oriented language. Do you have in mind some pattern or library for implementing aspects that implements marshaling behind the scenes?
Eric
I use PostSharp, so I define threading behavior in an attribute class and then use, say, [WpfThread] attribute in front of every method that has to be called on the UI thread.
Dmitri Nesteruk
That's fascinating ... I will have to try it out.
Eric