Avoiding the woes of Invoke/BeginInvoke in cross-thread WinForm event handling? Avoiding the woes of Invoke/BeginInvoke in cross-thread WinForm event handling? multithreading multithreading

Avoiding the woes of Invoke/BeginInvoke in cross-thread WinForm event handling?


Your scenario, as described, neatly fits BackgroundWorker - why not just use that? Your requirements for a solution are way too generic, and rather unreasonable - I doubt there is any solution that would satisfy them all.


I ran into this problem awhile back and came up with solution involving Synchronization Contexts. The solution is to add an extension method to SynchronizationContext which binds a particular delegate to the thread that the SynchronizationContext is bound to. It will generate a new delegate which when invoked will marshal the call to the appropraite thread and then call the original delegate. It makes it nearly impossible for consumers of the delegate to call it in the wrong context.

Blog post on the subject:


Ok, days later I've finished creating a solution. It solves all of the listed constraints and objectives in the initial post. The usage is simple and straight-forward:

myWorker.SomeEvent += new EventHandlerForControl<EventArgs>(this, myWorker_SomeEvent).EventHandler;

When the worker thread calls this event it will handle the required invocation to the control thread. It ensures that it will not hang indefinitely and will consistently throw an ObjectDisposedException if it is unable to execute on the control thread. I've created other derivations of the class, one to ignore the error, and another to directly call the delegate if the control is not available. Appears to work well and fully passes the several tests that reproduce the issues above. There is only one issue with the solution I can't prevent without violating constraint #3 above. This issue is the last (Update #4) in the issue description, the threading problems in get Handle. This can cause unexpected behavior on the original control thread, and I've regularly seen InvalidOperationException() thrown while calling Dispose() since the handle in the process of being created on my thread. To allow for dealing with this I ensure a lock around accessing functions that will use the Control.Handle property. This allows a form to overload the DestroyHandle method and lock prior to calling the base implementation. If this is done, this class should be entirely thread-safe (To the best of my knowledge).

public class Form : System.Windows.Forms.Form{    protected override void DestroyHandle()    {        lock (this) base.DestroyHandle();    }}

You may notice the core aspect of solving the dead-lock became a polling loop. Originally I successfully solved the test cases by handling the control's event for Disposed and HandleDestroyed and using multiple wait handles. After a more careful review, I found the subscription/unsubscription from these events is not thread-safe. Thus I chose to poll the IsHandleCreated instead so as not to create unneeded contention on the thread's events and thereby avoid the possibility of still producing a dead-lock state.

Anyway, here is the solution I came up with:

/// <summary>/// Provies a wrapper type around event handlers for a control that are safe to be/// used from events on another thread.  If the control is not valid at the time the/// delegate is called an exception of type ObjectDisposedExcpetion will be raised./// </summary>[System.Diagnostics.DebuggerNonUserCode]public class EventHandlerForControl<TEventArgs> where TEventArgs : EventArgs{    /// <summary> The control who's thread we will use for the invoke </summary>    protected readonly Control _control;    /// <summary> The delegate to invoke on the control </summary>    protected readonly EventHandler<TEventArgs> _delegate;    /// <summary>    /// Constructs an EventHandler for the specified method on the given control instance.    /// </summary>    public EventHandlerForControl(Control control, EventHandler<TEventArgs> handler)    {        if (control == null) throw new ArgumentNullException("control");        _control = control.TopLevelControl;        if (handler == null) throw new ArgumentNullException("handler");        _delegate = handler;    }    /// <summary>    /// Constructs an EventHandler for the specified delegate converting it to the expected    /// EventHandler<TEventArgs> delegate type.    /// </summary>    public EventHandlerForControl(Control control, Delegate handler)    {        if (control == null) throw new ArgumentNullException("control");        _control = control.TopLevelControl;        if (handler == null) throw new ArgumentNullException("handler");        //_delegate = handler.Convert<EventHandler<TEventArgs>>();        _delegate = handler as EventHandler<TEventArgs>;        if (_delegate == null)        {            foreach (Delegate d in handler.GetInvocationList())            {                _delegate = (EventHandler<TEventArgs>) Delegate.Combine(_delegate,                    Delegate.CreateDelegate(typeof(EventHandler<TEventArgs>), d.Target, d.Method, true)                );            }        }        if (_delegate == null) throw new ArgumentNullException("_delegate");    }    /// <summary>    /// Used to handle the condition that a control's handle is not currently available.  This    /// can either be before construction or after being disposed.    /// </summary>    protected virtual void OnControlDisposed(object sender, TEventArgs args)    {        throw new ObjectDisposedException(_control.GetType().Name);    }    /// <summary>    /// This object will allow an implicit cast to the EventHandler<T> type for easier use.    /// </summary>    public static implicit operator EventHandler<TEventArgs>(EventHandlerForControl<TEventArgs> instance)    { return instance.EventHandler; }    /// <summary>    /// Handles the 'magic' of safely invoking the delegate on the control without producing    /// a dead-lock.    /// </summary>    public void EventHandler(object sender, TEventArgs args)    {        bool requiresInvoke = false, hasHandle = false;        try        {            lock (_control) // locked to avoid conflicts with RecreateHandle and DestroyHandle            {                if (true == (hasHandle = _control.IsHandleCreated))                {                    requiresInvoke = _control.InvokeRequired;                    // must remain true for InvokeRequired to be dependable                    hasHandle &= _control.IsHandleCreated;                }            }        }        catch (ObjectDisposedException)        {            requiresInvoke = hasHandle = false;        }        if (!requiresInvoke && hasHandle) // control is from the current thread        {            _delegate(sender, args);            return;        }        else if (hasHandle) // control invoke *might* work        {            MethodInvokerImpl invocation = new MethodInvokerImpl(_delegate, sender, args);            IAsyncResult result = null;            try            {                lock (_control)// locked to avoid conflicts with RecreateHandle and DestroyHandle                    result = _control.BeginInvoke(invocation.Invoker);            }            catch (InvalidOperationException)            { }            try            {                if (result != null)                {                    WaitHandle handle = result.AsyncWaitHandle;                    TimeSpan interval = TimeSpan.FromSeconds(1);                    bool complete = false;                    while (!complete && (invocation.MethodRunning || _control.IsHandleCreated))                    {                        if (invocation.MethodRunning)                            complete = handle.WaitOne();//no need to continue polling once running                        else                            complete = handle.WaitOne(interval);                    }                    if (complete)                    {                        _control.EndInvoke(result);                        return;                    }                }            }            catch (ObjectDisposedException ode)            {                if (ode.ObjectName != _control.GetType().Name)                    throw;// *likely* from some other source...            }        }        OnControlDisposed(sender, args);    }    /// <summary>    /// The class is used to take advantage of a special-case in the Control.InvokeMarshaledCallbackDo()    /// implementation that allows us to preserve the exception types that are thrown rather than doing    /// a delegate.DynamicInvoke();    /// </summary>    [System.Diagnostics.DebuggerNonUserCode]    private class MethodInvokerImpl    {        readonly EventHandler<TEventArgs> _handler;        readonly object _sender;        readonly TEventArgs _args;        private bool _received;        public MethodInvokerImpl(EventHandler<TEventArgs> handler, object sender, TEventArgs args)        {            _received = false;            _handler = handler;            _sender = sender;            _args = args;        }        public MethodInvoker Invoker { get { return this.Invoke; } }        private void Invoke() { _received = true; _handler(_sender, _args); }        public bool MethodRunning { get { return _received; } }    }}

If you see anything wrong here, please let me know.