问题 取消后台任务


当我的C#应用​​程序关闭时,它有时会被清理程序捕获。具体来说,后台工作者没有关闭。这基本上就是我试图关闭它的方式:

    private void App_FormClosing(object sender,FormClosingEventArgs e)     {        backgroundWorker1.CancelAsync();        而(backgroundWorker1.IsBusy); //被困在这里     }

我应该采用不同的方式吗?我使用的是Microsoft Visual C#2008 Express Edition。谢谢。

附加信息:

后台工作人员似乎没有退出。这就是我所拥有的:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
   while (!backgroundWorker1.CancellationPending)
   {
      // Do something.
   }
}

我还修改了清理代码:

private void App_FormClosing(object sender, FormClosingEventArgs e)
{
   while (backgroundWorker1.IsBusy)
   {
      backgroundWorker1.CancelAsync();
      System.Threading.Thread.Sleep(1000);
   }
}

我还应该做些什么吗?


7852
2018-03-09 18:07


起源

你永远不应该运行像这样旋转的轮询循环,它将消耗CPU周期,使你的其他线程更难完成他们的工作。如果你不能使用WaitHandle或类似的同步原语,你绝对必须轮询,在那里添加一个Thread.Sleep(x)。 - Ian Mercer
+1来自@ Hightechrider的评论。您的应用程序不仅无法关闭,而且没有睡眠的时间会严重锁定整个系统。 - Kevin Gale
为了以防万一,您是否为BackgroundWorker将CanBeCancel或so属性设置为true? - Will Marcouiller
@Will:是的 WorkerSupportsCancellation 财产设定。 - Jim Fell
干脆做 backgroundWorker1.Dispose() 似乎要处理这个问题,因为我真正想做的就是释放系统资源,因为表单正在关闭。在这种情况下,是否有任何理由不应使用Dispose成员方法? - Jim Fell


答案:


凯文盖尔 表明您的BackgroundWorker的DoWork处理程序需要轮询CancellationPending并在请求取消时返回是正确的。

话虽如此,如果您的应用程序关闭时发生这种情况,您也可以安全地忽略它。 BackgroundWorker使用ThreadPool线程,根据定义,该线程是后台线程。保持此运行不会阻止您的应用程序终止,并且当您的应用程序关闭时,该线程将自动被拆除。


4
2018-03-09 18:23



+1绝对,除非后台工作线程在关闭之前需要执行某种类型的清理。 - Kevin Gale
@Kevin:是的,但由于OP只是试图取消线程,听起来并非如此...... - Reed Copsey
谢谢,里德。这似乎是我制作简单的东西比需要的更困难的情况。 - Jim Fell
@ReedCopsey在RCW清理上比赛怎么样? - techno


一些非常好的建议,但我不相信它们解决了潜在的问题:取消后台任务。

不幸的是,使用时 BackgroundWorker,任务的终止取决于任务本身。你的唯一方式 while 循环将终止,如果您的后台任务检查它 Cancel 财产,并从当前流程返回或中断。

示例基础

例如,考虑一下

private readonly BackgroundWorker worker = new BackgroundWorker ();

public void SomeFormEventForStartingBackgroundTask ()
{
    worker.DoWork += BackgroundTask_HotelCalifornia;
    worker.WorkerSupportsCancellation = true;
    worker.RunWorkerAsync ();
}

// semantically, you want to perform this task for lifetime of
// application, you may even expect that calling CancelAsync
// will out and out abort this method - that is incorrect.
// CancelAsync will only set DoWorkEventArgs.Cancel property
// to true
private void BackgroundTask_HotelCalifornia (object sender, DoWorkEventArgs e)
{
    for ( ; ;)
    {
        // because we never inspect e.Cancel, we can never leave!
    }
}

private void App_FormClosing(object sender, FormClosingEventArgs e)     
{
    // [politely] request termination
    worker.CancelAsync();

    // [politely] wait until background task terminates
    while (worker.IsBusy);
}

这是默认情况下发生的事情。现在,也许你的任务不是一个无限循环,也许它只是一个长期运行的任务。无论哪种方式,您的主线程将阻止[实际上它正在旋转,但是whatevs]直到任务完成,或者不是视情况而定。

如果您亲自编写并可以修改任务,那么您有几个选择。

示例改进

例如,这是上述示例的更好实现

private readonly BackgroundWorker worker = new BackgroundWorker ();

// this is used to signal our main Gui thread that background
// task has completed
private readonly AutoResetEvent isWorkerStopped = 
    new AutoResentEvent (false);

public void SomeFormEventForStartingBackgroundTask ()
{
    worker.DoWork += BackgroundTask_HotelCalifornia;
    worker.RunWorkerCompleted += BackgroundTask_Completed;
    worker.WorkerSupportsCancellation = true;
    worker.RunWorkerAsync ();
}

private void BackgroundTask_HotelCalifornia (object sender, DoWorkEventArgs e)
{
    // execute until canceled
    for ( ; !e.Cancel;)
    {
        // keep in mind, this task will *block* main
        // thread until cancel flag is checked again,
        // so if you are, say crunching SETI numbers 
        // here for instance, you could still be blocking
        // a long time. but long time is better than 
        // forever ;)
    }
}

private void BackgroundTask_Completed (
    object sender, 
    RunWorkerCompletedEventArgs e)
{
    // ok, our task has stopped, set signal to 'signaled' state
    // we are complete!
    isStopped.Set ();
}

private void App_FormClosing(object sender, FormClosingEventArgs e)     
{
    // [politely] request termination
    worker.CancelAsync();

    // [politely] wait until background task terminates
    isStopped.WaitOne ();
}

虽然这样做更好,但它并不尽如人意。如果您[合理]确保您的后台任务将结束,这可能“足够好”。

但是,我们[通常]想要的是这样的

private void App_FormClosing(object sender, FormClosingEventArgs e)     
{
    // [politely] request termination
    worker.CancelAsync();

    // [politely] wait until background task terminates
    TimeSpan gracePeriod = TimeSpan.FromMilliseconds(100);
    bool isStoppedGracefully = isStopped.WaitOne (gracePeriod);

    if (!isStoppedGracefully)
    {
        // KILL! KILL! KILL!
    }
}

唉,我们做不到。 BackgroundWorker 不会暴露任何强制终止手段。这是因为它是一个构建在一些隐藏线程管理系统之上的抽象,如果被强制终止,它可能会使应用程序的其他部分失去稳定性。

我实现上述目标的唯一方法是管理自己的线程。

示例理想

所以,例如

private Thread worker = null;

// this time, 'Thread' provides all synchronization
// constructs required for main thread to synchronize
// with background task. however, in the interest of
// giving background task a chance to terminate gracefully
// we supply it with this cancel signal
private readonly AutoResetEvent isCanceled = new AutoResentEvent (false);

public void SomeFormEventForStartingBackgroundTask ()
{
    worker = new Thread (BackgroundTask_HotelCalifornia);
    worker.IsBackground = true;
    worker.Name = "Some Background Task"; // always handy to name things!
    worker.Start ();
}

private void BackgroundTask_HotelCalifornia ()
{
    // inspect cancel signal, no wait period
    // 
    // NOTE: so cheating here a bit, this is an instance variable
    // but could as easily be supplied via parameterized thread
    // start delegate
    for ( ; !isCanceled.WaitOne (0);)
    {
    }
}

private void App_FormClosing(object sender, FormClosingEventArgs e)     
{
    // [politely] request termination
    isCanceled.Set ();

    // [politely] wait until background task terminates
    TimeSpan gracePeriod = TimeSpan.FromMilliseconds(100);
    bool isStoppedGracefully = worker.Join (gracePeriod);

    if (!isStoppedGracefully)
    {
        // wipe them out, all of them.
        worker.Abort ();
    }
}

那就是线程管理的一个不错的介绍。

哪个最适合你?取决于您的申请。这可能是最好的  摇滚船,并修改您当前的实施,以确保

  1. 你的后台任务检查和尊重 Cancel 属性
  2. 你的主线程等待完成,而不是轮询

比较和评估每种方法的优缺点非常重要。

如果你 必须 控制和保证终止 别人的 任务,然后编写包含上述内容的线程管理系统可能是最佳选择。但是你会失去开箱即用的功能,如线程池,进度报告,跨线程数据编组[工作人员那样做,没有?],以及其他一些东西。更不用说,“滚动你自己”往往容易出错。

无论如何,希望这有助于:)


7
2018-03-09 19:06



你害怕输入吗? while? - martijnn2008


在后台工作线程中,您需要检查BackgroundWorker.CancellationPending标志,如果为true,则退出。

CancelAsync()只设置此标志。

或者用另一种方式。 CancelAsync()实际上并没有取消任何东西。它不会中止线程或导致它退出。如果工作线程处于循环中并定期检查CancellationPending标志,则它可以捕获取消请求并退出。

MSDN有一个例子 这里 虽然它在工作程序中不使用循环。


4
2018-03-09 18:10



关闭,但不完全。你的后台任务应该检查[和尊重!]它 DoWorkEventArgs.Cancel 属性。除非您在线程之间共享实例变量 - 不明智 - 后台任务将无法访问实际的实例 BackgroundWorker。 - johnny g
Microsoft自己的示例通过sender参数访问后台工作程序。在任何情况下,我都没有为后台工作者引用他的变量,而是引用类的属性。为了更清楚,我添加了一个示例链接。 - Kevin Gale


当BGW仍在运行时,此代码保证死锁。在RunWorkerCompleted事件完成运行之前,BGW无法完成。在UI线程空闲并运行消息循环之前,RunWorkerCompleted无法运行。但UI线程不是空闲的,它停留在while循环中。

如果你想让BGW线程干净利落,那么你  保持你的形式活着。检查 这个帖子 看看如何做到这一点。


1
2018-03-09 19:01





尝试:

if (this.backgroundWorker1.IsBusy) this.backgroundWorker1.CancelAsync();

0
2018-03-09 18:14