简体   繁体   English

取消后台任务

[英]Cancelling Background Tasks

When my C# application closes it sometimes gets caught in the cleanup routine. 当我的C#应用​​程序关闭时,它有时会陷入清理例程。 Specifically, a background worker is not closing. 具体来说,后台工作人员没有关闭。 This is basically how I am attempting to close it: 这基本上就是我试图关闭它的方式:

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

Is there a different way that I should be doing this? 我应该采取不同的方式吗? I am using Microsoft Visual C# 2008 Express Edition. 我使用的是Microsoft Visual C#2008 Express Edition。 Thanks. 谢谢。

ADDITIONAL INFORMATION: 附加信息:

The background worker does not appear to be exiting. 后台工作人员似乎没有退出。 This is what I have: 这就是我所拥有的:

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

I've also modified the cleanup code: 我还修改了清理代码:

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

Is there something else that I should be doing? 我还应该做些什么吗?

Some pretty good suggestions, but I don't believe they address the underlying issue: canceling a background task. 一些非常好的建议,但我不认为它们解决了潜在的问题:取消后台任务。

Unfortunately, when using BackgroundWorker , termination of your task depends on the task itself. 不幸的是,使用BackgroundWorker ,任务的终止取决于任务本身。 The only way your while loop will terminate, is if your background task checks its Cancel property and returns or breaks from its current process. while循环终止的唯一方法是,如果后台任务检查其Cancel属性并从当前进程返回或中断。

Example Base 示例基础

For example, consider 例如,考虑一下

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

This is what is happening by default. 这是默认情况下发生的事情。 Now, maybe your task isn't an infinite loop, perhaps it is just a long-running task. 现在,也许你的任务不是一个无限循环,也许它只是一个长期运行的任务。 Either way, your main thread will block [actually it is spinning, but whatevs] until the task completes, or doesn't as the case may be. 无论哪种方式,您的主线程将阻止[实际上它正在旋转,但是whatevs]直到任务完成,或者不是视情况而定。

If you have personally written and can modify the task, then you have a few options. 如果您亲自编写并可以修改任务,那么您有几个选择。

Example Improvement 示例改进

For instance, this is a better implementation of the above example 例如,这是上述示例的更好实现

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

While this is better, it's not as good as it could be. 虽然这样做更好,但它并不是那么好。 If you can be [reasonably] assured your background task will end, this may be "good enough". 如果您[合理]确保您的后台任务将结束,这可能“足够好”。

However, what we [typically] want, is something like this 但是,我们[通常]想要的是这样的

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

Alas, we cannot. 唉,我们做不到。 BackgroundWorker does not expose any means of forceful termination. BackgroundWorker不会暴露任何强制终止的手段。 This is because it is an abstraction built on top of some hidden thread management system, one which could potentially destabalize other parts of your application if it were forcefully terminated. 这是因为它是一个构建在一些隐藏线程管理系统之上的抽象,如果它被强制终止,它可能会使应用程序的其他部分无法正常运行。

The only means [that I have seen at least] to implement the above is to manage your own threading. 我实现上述目标的唯一方法是管理自己的线程。

Example Ideal 示例理想

So, for instance 所以,例如

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

And that there, is a decent introduction on thread management. 那就是线程管理的一个不错的介绍。

Which is best suited for you? 哪个最适合你? Depends on your application. 取决于您的申请。 It is probably best not to rock the boat, and modify your current implementation to ensure that 最好不要晃动船只,并修改当前的实施以确保这一点

  1. your background task inspects and respects the Cancel property 您的后台任务检查并尊重Cancel属性
  2. your main thread waits for completion, as opposed to polling 你的主线程等待完成,而不是轮询

It is very important to compare and evaluate the pros and cons of each approach. 比较和评估每种方法的优缺点非常重要。

If you must control and guarantee termination of someone else's tasks, then writing a thread management system that incorporates the above may be the way to go. 如果您必须控制并保证终止其他人的任务,那么编写包含上述内容的线程管理系统可能就是您的选择。 However you would lose out on out-of-box features like thread pooling, progress reporting, cross-thread data marshalling [worker does that, no?], and a bunch of other stuff. 但是你会失去开箱即用的功能,如线程池,进度报告,跨线程数据编组[工作人员那样做,没有?],以及其他一些东西。 Not to mention, "rolling your own" is often error prone. 更不用说“滚动你自己”往往容易出错。

Anyway, hope this helps :) 无论如何,希望这有助于:)

In the background worker thread you need to check the BackgroundWorker.CancellationPending flag and exit if it is true. 在后台工作线程中,您需要检查BackgroundWorker.CancellationPending标志,如果为true,则退出。

The CancelAsync() just sets this flag. CancelAsync()只设置此标志。

Or to put it another way. 或者用另一种方式。 CancelAsync() doesn't actually cancel anything. CancelAsync()实际上并没有取消任何东西。 It won't abort the thread or cause it to exit. 它不会中止线程或导致它退出。 If the worker thread is in a loop and checks the CancellationPending flag periodically it can catch the cancel request and exit. 如果工作线程处于循环中并定期检查CancellationPending标志,则它可以捕获取消请求并退出。

MSDN has an example here although it doesn't use a loop in the worker routine. MSDN 在这里有一个例子虽然它没有在worker例程中使用循环。

Kevin Gale is correct in stating that your BackgroundWorker's DoWork handler needs to poll for CancellationPending and return if a cancellation is requested. Kevin Gale说明你的BackgroundWorker的DoWork处理程序需要轮询CancellationPending并在请求取消时返回是正确的。

That being said, if this is happening when your application is shutting down, you can just ignore it safely, as well. 话虽如此,如果您的应用程序关闭时发生这种情况,您也可以安全地忽略它。 BackgroundWorker uses a ThreadPool thread, which is, by definition, a background thread. BackgroundWorker使用ThreadPool线程,根据定义,该线程是后台线程。 Leaving this running will not prevent your application from terminating, and the thread will automatically be torn down when your application shuts down. 保持此运行不会阻止您的应用程序终止,并且当您的应用程序关闭时,该线程将自动被拆除。

This code is guaranteed to deadlock when the BGW is still running. 当BGW仍在运行时,此代码保证死锁。 BGW cannot complete until its RunWorkerCompleted event finished running. 在RunWorkerCompleted事件完成运行之前,BGW无法完成。 RunWorkerCompleted cannot run until the UI thread goes idle and runs the message loop. 在UI线程空闲并运行消息循环之前,RunWorkerCompleted无法运行。 But the UI thread isn't idle, it is stuck in the while loop. 但UI线程不是空闲的,它停留在while循环中。

If you want the BGW thread to complete cleanly, you have to keep your form alive. 如果你想让BGW线程干净利落,你必须让你的表格保持活力。 Check this thread to see how to do that. 检查此线程以了解如何执行此操作。

尝试:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM