C# Winforms Threading: Closed Form Gets Invoked

Yes, there's a race here. A takes a good millisecond before the target starts running. It will work 'better' if you use Control.BeginInvoke() instead, the form's Dispose() implementation will empty the dispatch queue. But that's still a race, albeit that it will strike very rarely. Your code as written in the snippet doesn't require Invoke().

The only clean fix is to interlock the FormClosing event and to delay the close until you got confirmation that the background thread is completed and can't be started again. Not easy to do with your code as is since that requires a 'completed' callback so you can really get the form closed. BackgroundWorker would be a better mousetrap. The Q&D fix is to catch the ObjectDisposedException that BeginInvoke will raise. Given how rare this will be when you use BeginInvoke(), that ugly hack could be acceptable. You just can't test it :)


I solved this synchronization issue for BeginInvoke by using Hans Passant's recommendation to catch the ObjectDisposedException. So far, it appears to work. I created extension methods of the Control class to facilitate this.

TryBeginInvoke attempts to invoke its own method on the control. If the method is successfully invoked, it checks whether the control has been disposed. If it has been disposed, it returns immediately; otherwise, it calls the method originally passed as a parameter to TryBeginInvoke. The code is as follows:

public static class ControlExtension
{
    // --- Static Fields ---
    static bool _fieldsInitialized = false;
    static InvokeDelegateDelegate _methodInvokeDelegate;  // Initialized lazily to reduce application startup overhead [see method: InitStaticFields]
    static InvokeMethodDelegate _methodInvokeMethod;  // Initialized lazily to reduce application startup overhead [see method: InitStaticFields]

    // --- Public Static Methods ---
    public static bool TryBeginInvoke(this Control control, Delegate method, params object[] args)
    {
        IAsyncResult asyncResult;
        return TryBeginInvoke(control, method, out asyncResult, args);
    }

    /// <remarks>May return true even if the target of the invocation cannot execute due to being disposed during invocation.</remarks>
    public static bool TryBeginInvoke(this Control control, Delegate method, out IAsyncResult asyncResult, params object[] args)
    {
        if (!_fieldsInitialized)
            InitStaticFields();

        asyncResult = null;

        if (!control.IsHandleCreated || control.IsDisposed)
            return false;

        try
        {
            control.BeginInvoke(_methodInvokeDelegate, control, method, args);
        }
        catch (ObjectDisposedException)
        {
            return false;
        }
        catch (InvalidOperationException)  // Handle not created
        {
            return false;
        }

        return true;
    }

    public static bool TryBeginInvoke(this Control control, MethodInvoker method)
    {
        IAsyncResult asyncResult;
        return TryBeginInvoke(control, method, out asyncResult);
    }

    /// <remarks>May return true even if the target of the invocation cannot execute due to being disposed during invocation.</remarks>
    public static bool TryBeginInvoke(this Control control, MethodInvoker method, out IAsyncResult asyncResult)
    {
        if (!_fieldsInitialized)
            InitStaticFields();

        asyncResult = null;

        if (!control.IsHandleCreated || control.IsDisposed)
            return false;

        try
        {
            control.BeginInvoke(_methodInvokeMethod, control, method);
        }
        catch (ObjectDisposedException)
        {
            return false;
        }
        catch (InvalidOperationException)  // Handle not created
        {
            return false;
        }

        return true;
    }

    // --- Private Static Methods ---
    private static void InitStaticFields()
    {
        _methodInvokeDelegate = new InvokeDelegateDelegate(InvokeDelegate);
        _methodInvokeMethod = new InvokeMethodDelegate(InvokeMethod);
    }
    private static object InvokeDelegate(Control control, Delegate method, object[] args)
    {
        if (!control.IsHandleCreated || control.IsDisposed)
            return null;

        return method.DynamicInvoke(args);
    }
    private static void InvokeMethod(Control control, MethodInvoker method)
    {
        if (!control.IsHandleCreated || control.IsDisposed)
            return;

        method();
    }

    // --- Private Nested Types ---
    delegate object InvokeDelegateDelegate(Control control, Delegate method, object[] args);
    delegate void InvokeMethodDelegate(Control control, MethodInvoker method);
}

Take a look at WindowsFormsSynchronizationContext. The Post method posts call to your UpdateUI delegate on the UI thread without needing a dedicated window; this lets you skip calling IsHandleCreated and Invoke.

Edit: MSDN has some code examples under "Multithreaded Programming with the Event-based Asynchronous Pattern".

You might find it easier to program via the AsyncOperationManager class, which sits on top of WindowsFormsSynchronizationContext. In turn, the BackgroundWorker component is built on top of AsyncOperationManager.

The UI thread is defined as the one on which you call AsyncOperationManager.CreateOperation; you want to call CreateOperation at the start of MyMethod, when you know you're on the UI thread, and capture its return value in a local variable.


You can check IsDisposed on the form (or any control) before Invoking on it.

You should also check this inside of the actual method you're Invoking, in case the form was disposed in the meantime.


Comments

  1. Forrest

    • 2016/8/15

    C programming is a general-purpose, procedural, imperative computer programming language developed in 1972 by Dennis M. Ritchie at the Bell Telephone 

  2. Dayton

    • 2015/1/22

    News Citigroup Inc.C. Consumer-Service Stocks Have Underperformed. Why It's Their Time to Shine. The Price to Earnings (P/E) ratio, a key valuation measure, is calculated by dividing the stock's

  3. Aarav

    • 2018/10/12

    C is a powerful general-purpose programming language. It can be used to develop software like operating systems, databases, compilers, and so on.

  4. Owen

    • 2018/12/1

    Citigroup, Inc. Common Stock (C) Stock Quotes - Nasdaq offers stock quotes & market activity data for US and global markets.

  5. Remy

    • 2019/1/22

    learn-c.org is a free interactive C tutorial for people who want to learn C, fast.

  6. Nicolas

    • 2016/5/28

    Discover historical prices for C stock on Yahoo Finance. View daily, weekly or monthly format back to when Citigroup, Inc. stock was issued.

  7. Jameson

    • 2018/6/13

    Amazon.com: C Programming Language, 2nd Edition: 8601410794231: Brian W. Kernighan, Dennis M. Ritchie: Books.

  8. Mancini

    • 2020/8/10

    The Centers for Disease Control and Prevention (CDC) cannot attest to the accuracy of a non-federal website. Linking to a non-federal website does not constitute an endorsement by CDC or any of its employees of the sponsors or the information and products presented on the website. You will be

  9. Reece

    • 2018/11/2

    C is considered as a middle-level language because it supports the feature of both low-level and high-level languages. C language program is converted into 

  10. Gatlin

    • 2018/8/10

    c = a + b; Here, ‘+’ is the operator known as addition operator and ‘a’ and ‘b’ are operands. The addition operator tells the compiler to add both of the operands ‘a’ and ‘b’. C/C++ has many built-in operator types and they are classified as follows:

  11. De Angelis

    • 2017/7/2

    The best site for C and C++ programming. Popular, beginner-friendly C and C++ tutorials to help you become an expert!

  12. Wesley

    • 2020/6/16

    C. diff (also known as Clostridioides difficile or C. difficile) is a germ (bacterium) that causes severe diarrhea and colitis (an inflammation of the colon). It’s estimated to cause almost half a million infections in the United States each year. About 1 in 6 patients who get C. diff will get it again in the subsequent 2-8 weeks.

Comments are closed.

Recent Posts