取消BackgroundWorker,如何在已经完成时防止竞争

时间:2010-01-08 20:17:52

标签: .net multithreading backgroundworker

我正在使用BackgroundWorker执行长计算(一次只有一次这样的计算)。

用户可以取消该工作人员(调用worker.CancelAsync)。

在worker.DoWork方法中,我定期检查取消挂起标志,然后从方法返回。

然后从工作人员处获得Completed事件,我可以检查该工作人员是否已被取消。此外,这是重要的事情,当我检测到取消时,我会做一些额外的清理工作。

我确信如果用户取消了工作者并且它已经从DoWork方法返回,则可能会出现问题。在那种情况下,我真的想知道工人被取消所以我可以清理......

有没有更好的方法来处理工作人员的清理取消程序?

5 个答案:

答案 0 :(得分:2)

您的DoWork事件处理程序应定期检查BackgroundWorker.CancellationPending,并在返回前将DoWorkEventArgs.Cancel设置为true。

您的RunWorkerCompleted事件处理程序应检查RunWorkerCompletedEventArgs.Cancelled属性以确定DoWork事件处理程序是否已取消(将DoWorkEventArgs.Cancel设置为true)。

如果出现竞争条件,可能会发生用户请求取消(BackgroundWorker.CancellationPending为真)但工作人员没有看到它(RunWorkerCompletedEventArgs.Cancelled为假)。您可以测试这两个属性以确定是否已发生这种情况,并执行您选择的任何操作(将其视为成功完成 - 因为工作人员确实已成功完成,或作为取消 - 因为用户已取消并且不关心任何更多)。

我没有看到发生什么事情有任何含糊之处的情况。

修改

在回复评论时 - 如果有几个类需要检测CancellationPending,那么实际上没有其他方法可以向这些类传递对BackgroundWorker等允许它们的类型的引用检索此信息。您可以将其抽象为一个界面,这是我通常在回答有关BackgroundWorkers的问题时所描述的。但是您仍然需要将对该接口实现的类型的引用传递给您的工作类。

如果您希望您的工作类能够设置DoWorkEventArgs.Cancel,您需要传递对此的引用,或者采用允许您的工作者的不同约定(例如布尔返回值或自定义异常)表示已取消取消的类。

答案 1 :(得分:2)

我不认为这里的其他答案实际上解决了所有情况下可能的竞争条件。问题是,无论DoWork是否真的有机会检查标志,在DoWork结束后,CancellationPending似乎都会被设置为false。如果你愿意,你可以自己测试一下。为了解决这个问题,我必须创建一个子类,如下所示:

Public Class CancelTrackingBackgroundWorker
  Inherits System.ComponentModel.BackgroundWorker
  Public CancelRequested As Boolean = False
  Public Sub TrackableCancelAsync()
    Me.CancelRequested = True
    Me.CancelAsync()
  End Sub
End Class

(顺便说一句,令人讨厌的CancelAsync是不可覆盖的。)然后我调用TrackableCancelAsync而不是旧的CancelAsync,我在我的代码中检查这个新的CancelRequested标志。只要你只从UI线程调用和检查(应该是标准的),那就应该处理你的线程问题。

答案 2 :(得分:1)

保证在RunWorkerCompleted事件处理程序中没有竞争,因为它在UI线程上运行。这应该始终有效:

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
  var bgw = sender as BackgroundWorker;
  if (e.Cancelled || bgw.CancellationPending) {
    // etc..
  }
}

答案 3 :(得分:0)

有没有理由不能在异步运行的方法结束时进行清理?

我通常使用像这样的结构

private void MyThreadMethod()
{
    try
    {
        // Thread code
    }
    finally 
    {
        // Cleanup
    }
}

我只使用complete事件来执行更新UI等任务,而不是在线程后进行清理。

答案 4 :(得分:0)

在没有看到您的代码的情况下,提出建议有点困难,但您可以做的一件事是在用户点击它时以及退出DoWork方法时禁用“取消”按钮。

如果用户也可以通过按键取消,那么您还需要禁用它。

另外 - 如果DoWork方法已经完成,那么用户没有“取消” - 或者我错过了什么。在这种情况下,即使你没有禁用取消按钮,也不需要做任何额外的事情。

为什么在工作人员完成工作后需要查看取消标志?你还想要回滚曾经改变过的东西吗?