51

I'm developing a class library in .NET that other developers will consume eventually. This library makes use of a few worker threads, and those threads fire status events that will cause some UI controls to be updated in the WinForms / WPF application.

Normally, for every update, you would need to check the .InvokeRequired property on WinForms or equivalent WPF property and invoke this on the main UI thread for updating. This can get old quickly, and something doesn't feel right about making the end developer do this, so...

Is there any way that my library can fire/invoke the events/delegates from the main UI thread?

In particular...

  1. Should I automatically "detect" the "main" thread to use?
  2. If not, should I require the end developer to call some (pseudo) UseThisThreadForEvents() method when the application starts so I can grab the target thread from that call?
Brandon
  • 13,956
  • 16
  • 72
  • 114
  • I assume you have ruled out using BackGroundworker ? cf. : http://stackoverflow.com/questions/1697784/changing-the-property-of-a-control-from-a-backgroundworker-c – BillW Nov 09 '09 at 03:17
  • @BillW, yes, there are somewhat complex requirements that rule out BackgroundWorker as a means to run the background threads. – Brandon Nov 09 '09 at 03:20

9 Answers9

40

Your library could check the Target of each delegate in the event's invocation list, and marshal the call to the target thread if that target is ISynchronizeInvoke:

private void RaiseEventOnUIThread(Delegate theEvent, object[] args)
{
  foreach (Delegate d in theEvent.GetInvocationList())
  {
    ISynchronizeInvoke syncer = d.Target as ISynchronizeInvoke;
    if (syncer == null)
    {
      d.DynamicInvoke(args);
    }
    else
    {
      syncer.BeginInvoke(d, args);  // cleanup omitted
    }
  }
}

Another approach, which makes the threading contract more explicit, is to require clients of your library to pass in an ISynchronizeInvoke or SynchronizationContext for the thread on which they want you to raise events. This gives users of your library a bit more visibility and control than the "secretly check the delegate target" approach.

In regard to your second question, I would place the thread marshalling stuff within your OnXxx or whatever API the user code calls that could result in an event being raised.

Brandon
  • 13,956
  • 16
  • 72
  • 114
itowlson
  • 73,686
  • 17
  • 161
  • 157
  • This worked beautifully; edited answer for minor syntax corrections, but thank you! – Brandon Nov 09 '09 at 16:20
  • I'm obviously doing something wrong, I get a `Parameter count mismatch.` when trying to implement this function. Could @itowlson or @Brandon edit this answer to show an example of how you would call this function from the newly created class please? I was trying something like `RaiseEventOnUIThread(MyCustomEvent, new object[] { myCustomEventArgs });` Note: In my situation the myCustomEventArgs object is of type NewCustomEventArgs. – Arvo Bowen Jun 10 '15 at 03:41
  • @arvo - what's your delegate signature for MyCustomEvent? How many parameters does it have? – Brandon Jun 10 '15 at 13:08
  • I think I understand how delegates work now based off the answer provided by @Mike Bouck. My signature is an EventArgs signature. So it would need to look like `RaiseEventOnUIThread(Delegate theEvent, object sender, EventArgs e)` for my situation I guess. I like the extension method better actually so I went with it. But that's for the response! – Arvo Bowen Jun 10 '15 at 18:08
  • 1
    may work for WinForms but not WPF. use theGecko's answer below. – joedotnot Jun 13 '15 at 08:25
28

Here's itwolson's idea expressed as an extension method which is working great for me:

/// <summary>Extension methods for EventHandler-type delegates.</summary>
public static class EventExtensions
{
    /// <summary>Raises the event (on the UI thread if available).</summary>
    /// <param name="multicastDelegate">The event to raise.</param>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">An EventArgs that contains the event data.</param>
    /// <returns>The return value of the event invocation or null if none.</returns>
    public static object Raise(this MulticastDelegate multicastDelegate, object sender, EventArgs e)
    {
        object retVal = null;

        MulticastDelegate threadSafeMulticastDelegate = multicastDelegate;
        if (threadSafeMulticastDelegate != null)
        {
            foreach (Delegate d in threadSafeMulticastDelegate.GetInvocationList())
            {
                var synchronizeInvoke = d.Target as ISynchronizeInvoke;
                if ((synchronizeInvoke != null) && synchronizeInvoke.InvokeRequired)
                {
                    retVal = synchronizeInvoke.EndInvoke(synchronizeInvoke.BeginInvoke(d, new[] { sender, e }));
                }
                else
                {
                    retVal = d.DynamicInvoke(new[] { sender, e });
                }
            }
        }

        return retVal;
    }
}

You then just raise your event like so:

MyEvent.Raise(this, EventArgs.Empty);
Mike Bouck
  • 488
  • 4
  • 8
  • I wondered why you created a copy of the multicastDelegate. I found the answer here: http://stackoverflow.com/questions/786383/c-sharp-events-and-thread-safety – Trevor Jan 19 '16 at 03:04
  • Or if you're using custom event args - `public static object Raise(this MulticastDelegate multicastDelegate, object sender, TEventArgs e) where TEventArgs : EventArgs` – stuartd Mar 23 '16 at 15:09
  • I don't think that making a copy makes ANY sense at all when you're using `this`. It cannot be null ever. – Tomáš Zato May 04 '18 at 02:24
13

You can use the SynchronizationContext class to marshall calls to the UI thread in WinForms or WPF by using SynchronizationContext.Current.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 6
    +1. You probably would of got more upvotes if you'd like to this- http://www.codeproject.com/KB/cpp/SyncContextTutorial.aspx – RichardOD Feb 09 '10 at 11:25
  • 1
    http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I http://www.codeproject.com/Articles/32113/Understanding-SynchronizationContext-Part-II – Daniel Dušek Nov 01 '14 at 09:59
  • 3
    This one-line statement really doesn't give any useful information. Showing how to actually use the class in a code sample would get my upvote. – Tim Long Jul 13 '15 at 01:46
11

I liked Mike Bouk's answer (+1) so much, I incorporated it into my codebase. I am concerned that his DynamicInvoke call will throw a runtime exception if the Delegate it invokes is not an EventHandler delegate, due to mismatched parameters. And since you're in a background thread, I assume you may want to call the UI method asynchronously and that you are not concerned with whether it ever finishes.

My version below can only be used with EventHandler delegates and will ignore other delegates in its invocation list. Since EventHandler delegates return nothing, we don't need the result. This allows me to call EndInvoke after the asynchronous process completes by passing the EventHandler in the BeginInvoke call. The call will return this EventHandler in IAsyncResult.AsyncState by way of the AsynchronousCallback, at which point EventHandler.EndInvoke is called.

/// <summary>
/// Safely raises any EventHandler event asynchronously.
/// </summary>
/// <param name="sender">The object raising the event (usually this).</param>
/// <param name="e">The EventArgs for this event.</param>
public static void Raise(this MulticastDelegate thisEvent, object sender, 
    EventArgs e)
{
  EventHandler uiMethod; 
  ISynchronizeInvoke target; 
  AsyncCallback callback = new AsyncCallback(EndAsynchronousEvent);

  foreach (Delegate d in thisEvent.GetInvocationList())
  {
    uiMethod = d as EventHandler;
    if (uiMethod != null)
    {
      target = d.Target as ISynchronizeInvoke; 
      if (target != null) target.BeginInvoke(uiMethod, new[] { sender, e }); 
      else uiMethod.BeginInvoke(sender, e, callback, uiMethod);
    }
  }
}

private static void EndAsynchronousEvent(IAsyncResult result) 
{ 
  ((EventHandler)result.AsyncState).EndInvoke(result); 
}

And the usage:

MyEventHandlerEvent.Raise(this, MyEventArgs);
hoodaticus
  • 3,772
  • 1
  • 18
  • 28
7

I found relying on the method being an EventHandler doesn't always work and ISynchronizeInvoke doesn't work for WPF. My attempt therefore looks like this, it may help someone:

public static class Extensions
{
    // Extension method which marshals events back onto the main thread
    public static void Raise(this MulticastDelegate multicast, object sender, EventArgs args)
    {
        foreach (Delegate del in multicast.GetInvocationList())
        {
            // Try for WPF first
            DispatcherObject dispatcherTarget = del.Target as DispatcherObject;
            if (dispatcherTarget != null && !dispatcherTarget.Dispatcher.CheckAccess())
            {
                // WPF target which requires marshaling
                dispatcherTarget.Dispatcher.BeginInvoke(del, sender, args);
            }
            else
            {
                // Maybe its WinForms?
                ISynchronizeInvoke syncTarget = del.Target as ISynchronizeInvoke;
                if (syncTarget != null && syncTarget.InvokeRequired)
                {
                    // WinForms target which requires marshaling
                    syncTarget.BeginInvoke(del, new object[] { sender, args });
                }
                else
                {
                    // Just do it.
                    del.DynamicInvoke(sender, args);
                }
            }
        }
    }
    // Extension method which marshals actions back onto the main thread
    public static void Raise<T>(this Action<T> action, T args)
    {
        // Try for WPF first
        DispatcherObject dispatcherTarget = action.Target as DispatcherObject;
        if (dispatcherTarget != null && !dispatcherTarget.Dispatcher.CheckAccess())
        {
            // WPF target which requires marshaling
            dispatcherTarget.Dispatcher.BeginInvoke(action, args);
        }
        else
        {
            // Maybe its WinForms?
            ISynchronizeInvoke syncTarget = action.Target as ISynchronizeInvoke;
            if (syncTarget != null && syncTarget.InvokeRequired)
            {
                // WinForms target which requires marshaling
                syncTarget.BeginInvoke(action, new object[] { args });
            }
            else
            {
                // Just do it.
                action.DynamicInvoke(args);
            }
        }
    }
}
theGecko
  • 1,033
  • 12
  • 22
5

You can store the dispatcher for the main thread in your library, use it to check if you are running on the UI thread, and execute on the UI thread through it if necessary.

The WPF threading documentation provides a good introduction and samples on how to do this.

Here is the gist of it:

private Dispatcher _uiDispatcher;

// Call from the main thread
public void UseThisThreadForEvents()
{
     _uiDispatcher = Dispatcher.CurrentDispatcher;
}

// Some method of library that may be called on worker thread
public void MyMethod()
{
    if (Dispatcher.CurrentDispatcher != _uiDispatcher)
    {
        _uiDispatcher.Invoke(delegate()
        {
            // UI thread code
        });
    }
    else
    {
         // UI thread code
    }
}
Anton
  • 3,170
  • 20
  • 20
  • I keep getting "anonymous method is not assignable to System.Delegate" when I try this. `.Invoke` won't accept a delegate like that. – mpen Mar 03 '12 at 18:09
3

I know this is an old thread, but seeing as it really helped me get started on building something similar, so I want to share my code. Using the new C#7 features, I was able to create a thread aware Raise function. It uses the EventHandler delegate template, and the C#7 pattern matching, and LINQ to filter and set type.

public static void ThreadAwareRaise<TEventArgs>(this EventHandler<TEventArgs> customEvent,
    object sender, TEventArgs e) where TEventArgs : EventArgs
{
    foreach (var d in customEvent.GetInvocationList().OfType<EventHandler<TEventArgs>>())
        switch (d.Target)
        {
            case DispatcherObject dispatchTartget:
                dispatchTartget.Dispatcher.BeginInvoke(d, sender, e);
                break;
            case ISynchronizeInvoke syncTarget when syncTarget.InvokeRequired:
                syncTarget.BeginInvoke(d, new[] {sender, e});
                break;
            default:
                d.Invoke(sender, e);
                break;
        }
}
Cory
  • 146
  • 8
1

I like these answers and examples but inherently by standard you are writing the library all wrong. It's important not to marshal your events to other threads for the sake of others. Keep your events fired where they are and handled where they belong. When the time comes for that event to change threads it's important to let the end developer do that at that point in time.

Michael
  • 45
  • 2
  • All these code examples test to see if a dynamic invoke is required before dynamically invoking the delegate on the UI thread. By incorporating this into the library you're saving anyone using that library from discovering the internal workings of the library utilize threads other than the main UI thread. – Mick Aug 08 '14 at 07:18
0

Resurrecting an old thread. Before you start a new thread you can save reference to the existing thread like so:

private SynchronizationContext synchronizationContext;
synchronizationContext = SynchronizationContext.Current;

Then in new thread you can refer back to the original thread and execute code there:

synchronizationContext.Post(new SendOrPostCallback((state) => {
                DoSomeStuff();
            }), null);

Everything in DoSomeStuff() will be executed in the original thread and if that means events firing that will be handled in winform app - so be it.

Bruno Batarelo
  • 315
  • 2
  • 11