首页 文章

取消后台任务

提问于
浏览
11

当我的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);
   }
}

我还应该做些什么吗?

5 回答

  • 4

    Kevin Gale表明您的BackgroundWorker的DoWork处理程序需要轮询CancellationPending并在请求取消时返回是正确的 .

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

  • 1

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

    不幸的是,当使用 BackgroundWorker 时,终止任务取决于任务本身 . while 循环终止的唯一方法是,如果后台任务检查其 Cancel 属性并从当前进程返回或中断 .

    Example Base

    例如,考虑一下

    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]直到任务完成,或者不是视情况而定 .

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

    Example Improvement

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

    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 没有暴露任何强行终止手段 . 这是因为它是一个构建在一些隐藏线程管理系统之上的抽象,如果它被强制终止,它可能会使应用程序的其他部分失去稳定性 .

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

    Example Ideal

    所以,例如

    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 ();
        }
    }
    

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

    哪个最适合你?取决于您的申请 . 最好不要晃动船只,并修改当前的实施以确保这一点

    • 您的后台任务检查并尊重 Cancel 属性

    • 你的主线程等待完成,而不是轮询

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

    如果您必须控制并保证终止其他人的任务,那么编写包含上述内容的线程管理系统可能就是您的选择 . 但是你会失去开箱即用的功能,如线程池,进度报告,跨线程数据编组[工作人员那样做,没有?],以及其他一些东西 . 更不用说,"rolling your own"经常容易出错 .

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

  • 7

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

    CancelAsync()只设置此标志 .

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

    MSDN有一个示例here虽然它没有在worker例程中使用循环 .

  • 0

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

    如果你想让BGW线程干净利落,你必须让你的表格保持活力 . 校验this thread看看如何做到这一点 .

  • 4

    尝试:

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

相关问题