81

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
}
Nick
  • 13,238
  • 17
  • 64
  • 100
  • 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 Apr 29 '09 at 17:30

10 Answers10

45

I have some code for this online. It's much nicer 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
  • 110,262
  • 41
  • 219
  • 271
  • You could also change the namespace to `System.Windows.Forms` in your extension. This way you avoid adding the _your custom namespace_ every time you need it. – Joe Almore Feb 02 '15 at 12:43
29

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<CoolObjectEventArgs>(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.

  • I would probably favor 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.

Naser Asadi
  • 1,153
  • 18
  • 35
Shaun Austin
  • 3,782
  • 3
  • 23
  • 27
12

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
  • 530,221
  • 131
  • 937
  • 1,214
6

I made the following 'universal' cross thread call class for my own purpose, but I think it's worth to share it:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace CrossThreadCalls
{
  public static class clsCrossThreadCalls
  {
    private delegate void SetAnyPropertyCallBack(Control c, string Property, object Value);
    public static void SetAnyProperty(Control c, string Property, object Value)
    {
      if (c.GetType().GetProperty(Property) != null)
      {
        //The given property exists
        if (c.InvokeRequired)
        {
          SetAnyPropertyCallBack d = new SetAnyPropertyCallBack(SetAnyProperty);
          c.BeginInvoke(d, c, Property, Value);
        }
        else
        {
          c.GetType().GetProperty(Property).SetValue(c, Value, null);
        }
      }
    }

    private delegate void SetTextPropertyCallBack(Control c, string Value);
    public static void SetTextProperty(Control c, string Value)
    {
      if (c.InvokeRequired)
      {
        SetTextPropertyCallBack d = new SetTextPropertyCallBack(SetTextProperty);
        c.BeginInvoke(d, c, Value);
      }
      else
      {
        c.Text = Value;
      }
    }
  }

And you can simply use SetAnyProperty() from another thread:

CrossThreadCalls.clsCrossThreadCalls.SetAnyProperty(lb_Speed, "Text", KvaserCanReader.GetSpeed.ToString());

In this example the above KvaserCanReader class runs its own thread and makes a call to set the text property of the lb_Speed label on the main form.

TarPista
  • 69
  • 1
  • 1
3

Use the synchronisation context if you want to send a result to the UI thread. I needed to change the thread priority so I changed from using thread pool threads (commented out code) and created a new thread of my own. I was still able to use the synchronisation context to return whether the database cancel succeeded or not.

    #region SyncContextCancel

    private SynchronizationContext _syncContextCancel;

    /// <summary>
    /// Gets the synchronization context used for UI-related operations.
    /// </summary>
    /// <value>The synchronization context.</value>
    protected SynchronizationContext SyncContextCancel
    {
        get { return _syncContextCancel; }
    }

    #endregion //SyncContextCancel

    public void CancelCurrentDbCommand()
    {
        _syncContextCancel = SynchronizationContext.Current;

        //ThreadPool.QueueUserWorkItem(CancelWork, null);

        Thread worker = new Thread(new ThreadStart(CancelWork));
        worker.Priority = ThreadPriority.Highest;
        worker.Start();
    }

    SQLiteConnection _connection;
    private void CancelWork()//object state
    {
        bool success = false;

        try
        {
            if (_connection != null)
            {
                log.Debug("call cancel");
                _connection.Cancel();
                log.Debug("cancel complete");
                _connection.Close();
                log.Debug("close complete");
                success = true;
                log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());
            }
        }
        catch (Exception ex)
        {
            log.Error(ex.Message, ex);
        }

        SyncContextCancel.Send(CancelCompleted, new object[] { success });
    }

    public void CancelCompleted(object state)
    {
        object[] args = (object[])state;
        bool success = (bool)args[0];

        if (success)
        {
            log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());

        }
    }
The Lonely Coder
  • 613
  • 9
  • 12
3

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
  • 23,067
  • 22
  • 97
  • 166
  • 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 May 05 '09 at 18:08
  • 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 May 06 '09 at 05:59
2

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);
}
  • 2
    Performing a BeginInvoke within a GUI thread will cause the action in question to be deferred until the next time the UI thread processes Windows messages. This can actually be a useful thing to do in some cases. – supercat Apr 17 '11 at 22:02
2

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
  • 8,455
  • 6
  • 35
  • 30
  • this wont work. once you set the prop on the non UI thread you get exception.. ie Name="gbc" bang! failure... there is no free cheese mate – Boppity Bop Nov 23 '11 at 11:40
  • It's not free (it costs execution time), but the wpf binding machinery does appear to handle cross-thread marshalling automatically. We use this a lot with props that are updated by network data received on background threads. There's an explanaction here: http://blog.lab49.com/archives/1166 – gbc Nov 27 '11 at 21:43
  • 1
    @gbc Aaaaand explanation gone 404. – Jan 'splite' K. Sep 11 '17 at 09:23
0

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
  • 4,376
  • 2
  • 23
  • 30
-3

I am using something like

Invoke((Action)(() =>
        {
            //your code
        }));
mahirgul
  • 1
  • 1