Raise Events in .NET on the main UI thread

Viewed 35809

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?
8 Answers

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);

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;
        }
}
Related