C# 如何在窗体的关闭事件中停止BackgroundWorker?

C# 如何在窗体的关闭事件中停止BackgroundWorker?,c#,winforms,multithreading,backgroundworker,C#,Winforms,Multithreading,Backgroundworker,我有一个生成BackgroundWorker的表单,它应该在主线程上更新表单自己的文本框,从而调用。。。;呼叫 如果在HandleClosingEvent中我只执行bgWorker.CancelAsync,那么我在调用时会得到ObjectDisposedException。。。打电话,可以理解。但如果我坐在HandleClosingEvent中等待bgWorker完成,那么.Invoke。。。永远不会回来,这也是可以理解的 你知道如何关闭此应用程序而不出现异常或死锁吗 以下是simple For

我有一个生成BackgroundWorker的表单,它应该在主线程上更新表单自己的文本框,从而调用。。。;呼叫 如果在HandleClosingEvent中我只执行bgWorker.CancelAsync,那么我在调用时会得到ObjectDisposedException。。。打电话,可以理解。但如果我坐在HandleClosingEvent中等待bgWorker完成,那么.Invoke。。。永远不会回来,这也是可以理解的

你知道如何关闭此应用程序而不出现异常或死锁吗

以下是simple Form1类的3种相关方法:

    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
    }

一个可行但太复杂的解决方案。这个想法是产生一个计时器,它将继续尝试关闭窗体,而窗体将拒绝关闭,直到所述bgWorker死亡


我会将与文本框关联的SynchronizationContext传递给BackgroundWorker,并使用它在UI线程上执行更新。使用SynchronizationContext.Post,您可以检查控件是否已释放或正在释放。

您不能等待表单析构函数中的信号吗

AutoResetEvent workerDone = new AutoResetEvent();

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

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


private ~Form1()
{
    workerDone.WaitOne();
}


void backgroundWorker1_RunWorkerCompleted( Object sender, RunWorkerCompletedEventArgs e )
{
    workerDone.Set();
}

我所知道的唯一一种死锁安全和异常安全的方法是实际取消FormClosing事件。如果BGW仍在运行,则设置e.Cancel=true,并设置标志以指示用户请求关闭。然后检查BGW的RunWorkerCompleted事件处理程序中的该标志,如果设置了该标志,则调用Close

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...
}

首先,ObjectDisposedException只是一个可能的陷阱。在大量情况下,运行OP代码会产生以下InvalidOperationException:

无法调用Invoke或BeginInvoke 在控件上,直到窗口句柄 已创建

我认为这可以通过在“已加载”回调上启动worker而不是构造函数来进行修改,但是如果使用BackgroundWorker的进度报告机制,则可以完全避免这种痛苦。以下措施效果良好:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    while (!this.bgWorker.CancellationPending)
    {
        this.bgWorker.ReportProgress(Environment.TickCount);
        Thread.Sleep(1);
    }
}

private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    this.textBox1.Text = e.ProgressPercentage.ToString();
}
我有点劫持了percentage参数,但是一个可以使用另一个重载来传递任何参数

有趣的是,删除上面的睡眠调用会阻塞UI,消耗大量CPU,并不断增加内存使用。我想这与GUI的消息队列过载有关。然而,在睡眠调用保持不变的情况下,CPU的使用率实际上为0,内存的使用率似乎也很好。为谨慎起见,是否应使用高于1ms的值?请在此提供专家意见。。。更新:似乎只要更新不太频繁,就可以:


在任何情况下,我都无法预见GUI更新的时间间隔至少要短于几毫秒的场景,在人类正在观看GUI的场景中,因此我认为大多数时间进度报告都是正确的选择

这是我的解决方案,抱歉,它在VB.Net中

当我运行FormClosing事件时,我运行BackgroundWorker1.CancelAsync将CancellationPending值设置为True。不幸的是,该程序从未真正有机会检查值CancellationPending值以将e.Cancel设置为true,据我所知,这只能在BackgroundWorker1\u DoWork中完成。 我没有删除那条线,尽管它看起来并没有什么不同

我添加了一行代码,将全局变量bClosingForm设置为True。然后,我在BackgroundWorker_WorkCompleted中添加了一行代码,以在执行任何结束步骤之前检查e.Cancelled和全局变量bClosingForm

使用这个模板,你应该能够随时关闭你的窗体,即使后台工作人员在一些不好的东西中间,但是它必然会发生,所以它也可以被处理。我不确定是否有必要,但在这一切发生后,您可以在Form_Closed事件中完全处理后台工作程序

Private bClosingForm As Boolean = False

Private Sub SomeFormName_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
    bClosingForm = True
    BackgroundWorker1.CancelAsync() 
End Sub

Private Sub backgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    'Run background tasks:
    If BackgroundWorker1.CancellationPending Then
        e.Cancel = True
    Else
        'Background work here
    End If
End Sub

Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
    If Not bClosingForm Then
        If Not e.Cancelled Then
            'Completion Work here
        End If
    End If
End Sub

我找到了另一种方法。如果你有更多的背景员工,你可以:

List<Thread> bgWorkersThreads  = new List<Thread>();
您可以使用的程序:

foreach (Thread thread in this.bgWorkersThreads) 
{
     thread.Abort();    
}
我在Word外接程序控件中使用了它,我在CustomTaskPane中使用了它。如果有人提前关闭文档或应用程序,而我的所有后台工作人员都完成了他们的工作,则会引发一些COM异常,我不记得哪个.CancelAsync不工作

但是有了这个,我可以在DocumentBeforeClose事件中立即关闭backgroundworkers使用的所有线程,我的问题就解决了。

另一种方法:

if (backgroundWorker.IsBusy)
{
    backgroundWorker.CancelAsync();
    while (backgroundWorker.IsBusy)
    {
        Application.DoEvents();
    }
}

那我呢,我是山德勒创造的

    Private Sub BwDownload_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BwDownload.RunWorkerCompleted
    If Me.IsHandleCreated Then
        'Form is still open, so proceed
    End If
End Sub

您的backgroundworker不应使用Invoke来更新文本框。它应该很好地要求UI线程使用EventProgressChanged更新文本框,并将值放入附加的文本框中

在事件关闭或事件关闭期间,UI线程会记住表单在取消backgroundworker之前已关闭

收到progressChanged后,UI线程检查表单是否已关闭,只有在未关闭时,才会更新文本框。

这并不适用于所有人,但如果您定期在BackgroundWorker中执行某些操作,例如每秒钟或每10秒,可能轮询服务器,这似乎可以很好地以有序的方式停止进程,至少到目前为止没有错误消息,并且易于遵循

 public void StopPoll()
        {
            MyBackgroundWorker.CancelAsync(); //Cancel background worker
            AutoResetEvent1.Set(); //Release delay so cancellation occurs soon
        }

 private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            while (!MyBackgroundWorker.CancellationPending)
            {
            //Do some background stuff
            MyBackgroundWorker.ReportProgress(0, (object)SomeData);
            AutoResetEvent1.WaitOne(10000);
            }
    }

我真的不明白,在这种情况下,如果您使用此选项,为什么DoEvents会被视为如此糟糕的选择。enabled=false。我想这会使它很整洁

protected override void OnFormClosing(FormClosingEventArgs e) {

    this.Enabled = false;   // or this.Hide()
    e.Cancel = true;
    backgroundWorker1.CancelAsync();  

    while (backgroundWorker1.IsBusy) {

        Application.DoEvents();

    }

    e.cancel = false;
    base.OnFormClosing(e);

}

WindowsFormsSynchronizationContext.Post。。。只是调用BeginInvoke…,所以它和我已经在做的调用没什么不同。除非我遗漏了什么,你能详细说明一下吗?你是否尝试使用BegingVoke而不是Invoke,这样你就不必等到invokemessage返回了?是的。没有死锁,但我不知道在主线程上何时处理BeginInvoke,所以我回到ObjectDispostedException。这很有效。我使用了workersThread.CancellationPending+workersThread.IsBusy标志,而不是McCompleted。这有点危险,IsBusy是异步线程的属性。它可以比赛。实际上没有,但那是运气。此外,CancellationPending在RunWorkerCompleted激发之前重置。次要信息:您需要告诉BackGroundWorker实例它可以被取消。说到种族。。。如果工人恰好在if之后正常完成,这不是无法关闭吗!McCompleted?@lain:否,OnFormClosing和backgroundWorker1\u RunWorkerCompleted都在UI线程上运行。一个不能被另一个打断。+1在BackgroundWorker的RunWorkerCompleted EventHandler中处理此问题。我就是这么做的这听起来很烦人。不会这样做。在我的do…WhileIsBusy检查循环中添加DoEvents效果很好。我的后台工作程序中运行的循环(包括取消挂起的检查)非常快。0004毫秒。不确定这是否是它可靠的原因。DoEvents是如此普遍地被诽谤和诅咒好的编码,以至于我完全忘记了它的存在!!非常感谢你的建议!
 public void StopPoll()
        {
            MyBackgroundWorker.CancelAsync(); //Cancel background worker
            AutoResetEvent1.Set(); //Release delay so cancellation occurs soon
        }

 private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            while (!MyBackgroundWorker.CancellationPending)
            {
            //Do some background stuff
            MyBackgroundWorker.ReportProgress(0, (object)SomeData);
            AutoResetEvent1.WaitOne(10000);
            }
    }
protected override void OnFormClosing(FormClosingEventArgs e) {

    this.Enabled = false;   // or this.Hide()
    e.Cancel = true;
    backgroundWorker1.CancelAsync();  

    while (backgroundWorker1.IsBusy) {

        Application.DoEvents();

    }

    e.cancel = false;
    base.OnFormClosing(e);

}