Search code examples
c#winformsmultithreadingbackgroundworker

How to stop BackgroundWorker on Form's Closing event?


I have a form that spawns a BackgroundWorker, that should update form's own textbox (on main thread), hence Invoke((Action) (...)); call.
If in HandleClosingEvent I just do bgWorker.CancelAsync() then I get ObjectDisposedException on Invoke(...) call, understandably. But if I sit in HandleClosingEvent and wait for bgWorker to be done, than .Invoke(...) never returns, also understandably.

Any ideas how do I close this app without getting the exception, or the deadlock?

Following are 3 relevant methods of the simple Form1 class:

    public Form1() {
        InitializeComponent();
        Closing += HandleClosingEvent;
        this.bgWorker.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
        while (!this.bgWorker.CancellationPending) {
            Invoke((Action) (() => { this.textBox1.Text = Environment.TickCount.ToString(); }));
        }
    }

    private void HandleClosingEvent(object sender, CancelEventArgs e) {
        this.bgWorker.CancelAsync();
        /////// while (this.bgWorker.CancellationPending) {} // deadlock
    }

Solution

  • The only deadlock-safe and exception-safe way to do this that I know is to actually cancel the FormClosing event. Set e.Cancel = true if the BGW is still running and set a flag to indicate that the user requested a close. Then check that flag in the BGW's RunWorkerCompleted event handler and call Close() if it is set.

    private bool closePending;
    
    protected override void OnFormClosing(FormClosingEventArgs e) {
        if (backgroundWorker1.IsBusy) {
            closePending = true;
            backgroundWorker1.CancelAsync();
            e.Cancel = true;
            this.Enabled = false;   // or this.Hide()
            return;
        }
        base.OnFormClosing(e);
    }
    
    void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
        if (closePending) this.Close();
        closePending = false;
        // etc...
    }