C# 如何等待 BackgroundWorker 取消?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/123661/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 14:38:19  来源:igfitidea点击:

How to wait for a BackgroundWorker to cancel?

提问by Ian Boyd

Consider a hypotheticalmethod of an object that does stuff for you:

考虑一个为你做事的对象的假设方法:

public class DoesStuff
{
    BackgroundWorker _worker = new BackgroundWorker();

    ...

    public void CancelDoingStuff()
    {
        _worker.CancelAsync();

        //todo: Figure out a way to wait for BackgroundWorker to be cancelled.
    }
}

How can one wait for a BackgroundWorker to be done?

如何等待 BackgroundWorker 完成?



In the past people have tried:

过去人们曾尝试过:

while (_worker.IsBusy)
{
    Sleep(100);
}

But this deadlocks, because IsBusyis not cleared until after the RunWorkerCompletedevent is handled, and that event can't get handled until the application goes idle. The application won't go idle until the worker is done. (Plus, it's a busy loop - disgusting.)

但是这个死锁,因为IsBusy直到RunWorkerCompleted事件被处理之后才会被清除,并且在应用程序空闲之前无法处理该事件。在工作程序完成之前,应用程序不会闲置。(另外,这是一个繁忙的循环 - 恶心。)

Others have add suggested kludging it into:

其他人已添加建议将其合并为:

while (_worker.IsBusy)
{
    Application.DoEvents();
}

The problem with that is that is Application.DoEvents()causes messages currently in the queue to be processed, which cause re-entrancy problems (.NET isn't re-entrant).

这样做的问题是Application.DoEvents()导致当前队列中的消息被处理,这会导致重入问题(.NET 不可重入)。

I would hope to use some solution involving Event synchronization objects, where the code waitsfor an event - that the worker's RunWorkerCompletedevent handlers sets. Something like:

我希望使用一些涉及事件同步对象的解决方案,其中代码等待事件 - 工作人员的RunWorkerCompleted事件处理程序设置。就像是:

Event _workerDoneEvent = new WaitHandle();

public void CancelDoingStuff()
{
    _worker.CancelAsync();
    _workerDoneEvent.WaitOne();
}

private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
    _workerDoneEvent.SetEvent();
}

But I'm back to the deadlock: the event handler can't run until the application goes idle, and the application won't go idle because it's waiting for an Event.

但是我又回到了僵局:事件处理程序在应用程序空闲之前无法运行,并且应用程序不会因为它在等待事件而空闲。

So how can you wait for an BackgroundWorker to finish?

那么你怎么能等待一个BackgroundWorker完成呢?



UpdatePeople seem to be confused by this question. They seem to think that I will be using the BackgroundWorker as:

更新人们似乎对这个问题感到困惑。他们似乎认为我将使用 BackgroundWorker 作为:

BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += MyWork;
worker.RunWorkerAsync();
WaitForWorkerToFinish(worker);

That is notit, that is notwhat I'm doing, and that is notwhat is being asked here. If that were the case, there would be no point in using a background worker.

这是不是它,那是不是我在做什么,那就是没有什么被要求在这里。如果是这样,那么使用后台工作人员就没有意义了。

采纳答案by Fredrik Kalseth

If I understand your requirement right, you could do something like this (code not tested, but shows the general idea):

如果我理解你的要求,你可以做这样的事情(代码未经测试,但显示了总体思路):

private BackgroundWorker worker = new BackgroundWorker();
private AutoResetEvent _resetEvent = new AutoResetEvent(false);

public Form1()
{
    InitializeComponent();

    worker.DoWork += worker_DoWork;
}

public void Cancel()
{
    worker.CancelAsync();
    _resetEvent.WaitOne(); // will block until _resetEvent.Set() call made
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    while(!e.Cancel)
    {
        // do something
    }

    _resetEvent.Set(); // signal that worker is done
}

回答by Seb Nilsson

You can check into the RunWorkerCompletedEventArgsin the RunWorkerCompletedEventHandlerto see what the status was. Success, canceled or an error.

您可以检查RunWorkerCompletedEventHandler中的RunWorkerCompletedEventArgs以查看状态。成功、取消或错误。

private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
    if(e.Cancelled)
    {
        Console.WriteLine("The worker was cancelled.");
    }
}

Update: To see if your worker has called .CancelAsync() by using this:

更新:要查看您的工作人员是否使用以下方法调用了 .CancelAsync():

if (_worker.CancellationPending)
{
    Console.WriteLine("Cancellation is pending, no need to call CancelAsync again");
}

回答by Rick Minerich

Why can't you just tie into the BackgroundWorker.RunWorkerCompleted Event. It's a callback that will "Occur when the background operation has completed, has been canceled, or has raised an exception."

为什么不能直接绑定到 BackgroundWorker.RunWorkerCompleted 事件。这是一个回调,将“在后台操作完成、取消或引发异常时发生”。

回答by Austin Salonen

I don't understand why you'd want to wait for a BackgroundWorker to complete; it really seems like the exact opposite of the motivation for the class.

我不明白你为什么要等待 BackgroundWorker 完成;这似乎与上课的动机完全相反。

However, you could start every method with a call to worker.IsBusy and have them exit if it is running.

但是,您可以通过调用 worker.IsBusy 来启动每个方法,并让它们在运行时退出。

回答by Stephan

Hm maybe I am not getting your question right.

嗯,也许我没有正确回答你的问题。

The backgroundworker calls the WorkerCompleted event once his 'workermethod' (the method/function/sub that handles the backgroundworker.doWork-event) is finished so there is no need for checking if the BW is still running. If you want to stop your worker check the cancellation pending propertyinside your 'worker method'.

一旦他的“workermethod”(处理backgroundworker.doWork-event的方法/函数/子)完成,后台工作人员就会调用 WorkerCompleted 事件, 因此无需检查 BW 是否仍在运行。如果您想停止您的工作人员,请检查您的“工作人员方法”中的取消挂起属性

回答by Joel Coehoorn

You don'twait for the background worker to complete. That pretty much defeats the purpose of launching a separate thread. Instead, you should let your method finish, and move any code that depends on completion to a different place. You let the worker tell you when it's done and call any remaining code then.

不必等待后台工作人员完成。这几乎违背了启动单独线程的目的。相反,您应该让您的方法完成,并将任何依赖于完成的代码移动到不同的地方。你让工作人员告诉你什么时候完成,然后调用任何剩余的代码。

If you want to waitfor something to complete use a different threading construct that provides a WaitHandle.

如果您想等待某事完成,请使用提供 WaitHandle 的不同线程构造。

回答by OwenP

The workflow of a BackgroundWorkerobject basically requires you to handle the RunWorkerCompletedevent for both normal execution and user cancellation use cases. This is why the property RunWorkerCompletedEventArgs.Cancelledexists. Basically, doing this properly requires that you consider your Cancel method to be an asynchronous method in itself.

BackgroundWorker对象的工作流基本上要求您处理RunWorkerCompleted正常执行和用户取消用例的事件。这就是属性RunWorkerCompletedEventArgs.Cancelled存在的原因。基本上,正确执行此操作需要您将 Cancel 方法本身视为一种异步方法。

Here's an example:

下面是一个例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;

namespace WindowsFormsApplication1
{
    public class AsyncForm : Form
    {
        private Button _startButton;
        private Label _statusLabel;
        private Button _stopButton;
        private MyWorker _worker;

        public AsyncForm()
        {
            var layoutPanel = new TableLayoutPanel();
            layoutPanel.Dock = DockStyle.Fill;
            layoutPanel.ColumnStyles.Add(new ColumnStyle());
            layoutPanel.ColumnStyles.Add(new ColumnStyle());
            layoutPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
            layoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100));

            _statusLabel = new Label();
            _statusLabel.Text = "Idle.";
            layoutPanel.Controls.Add(_statusLabel, 0, 0);

            _startButton = new Button();
            _startButton.Text = "Start";
            _startButton.Click += HandleStartButton;
            layoutPanel.Controls.Add(_startButton, 0, 1);

            _stopButton = new Button();
            _stopButton.Enabled = false;
            _stopButton.Text = "Stop";
            _stopButton.Click += HandleStopButton;
            layoutPanel.Controls.Add(_stopButton, 1, 1);

            this.Controls.Add(layoutPanel);
        }

        private void HandleStartButton(object sender, EventArgs e)
        {
            _stopButton.Enabled = true;
            _startButton.Enabled = false;

            _worker = new MyWorker() { WorkerSupportsCancellation = true };
            _worker.RunWorkerCompleted += HandleWorkerCompleted;
            _worker.RunWorkerAsync();

            _statusLabel.Text = "Running...";
        }

        private void HandleStopButton(object sender, EventArgs e)
        {
            _worker.CancelAsync();
            _statusLabel.Text = "Cancelling...";
        }

        private void HandleWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                _statusLabel.Text = "Cancelled!";
            }
            else
            {
                _statusLabel.Text = "Completed.";
            }

            _stopButton.Enabled = false;
            _startButton.Enabled = true;
        }

    }

    public class MyWorker : BackgroundWorker
    {
        protected override void OnDoWork(DoWorkEventArgs e)
        {
            base.OnDoWork(e);

            for (int i = 0; i < 10; i++)
            {
                System.Threading.Thread.Sleep(500);

                if (this.CancellationPending)
                {
                    e.Cancel = true;
                    e.Result = false;
                    return;
                }
            }

            e.Result = true;
        }
    }
}

If you really reallydon't want your method to exit, I'd suggest putting a flag like an AutoResetEventon a derived BackgroundWorker, then override OnRunWorkerCompletedto set the flag. It's still kind of kludgy though; I'd recommend treating the cancel event like an asynchronous method and do whatever it's currently doing in the RunWorkerCompletedhandler.

如果你真的不希望你的方法退出,我建议AutoResetEvent在衍生的上放置一个像 an 的标志BackgroundWorker,然后覆盖OnRunWorkerCompleted设置标志。尽管如此,它仍然有点笨拙。我建议将取消事件视为异步方法,并执行RunWorkerCompleted处理程序中当前正在执行的任何操作。

回答by Joe

There is a problem with thisresponse. The UI needs to continue to process messages while you are waiting, otherwise it will not repaint, which will be a problem if your background worker takes a long time to respond to the cancel request.

这个回复有问题。UI需要在你等待的时候继续处理消息,否则不会重绘,如果你的后台worker需要很长时间来响应取消请求就会有问题。

A second flaw is that _resetEvent.Set()will never be called if the worker thread throws an exception - leaving the main thread waiting indefinitely - however this flaw could easily be fixed with a try/finally block.

第二个缺陷是,_resetEvent.Set()如果工作线程抛出异常,它将永远不会被调用——让主线程无限期地等待——但是这个缺陷可以很容易地用 try/finally 块修复。

One way to do this is to display a modal dialog which has a timer that repeatedly checks if the background worker has finished work (or finished cancelling in your case). Once the background worker has finished, the modal dialog returns control to your application. The user can't interact with the UI until this happens.

一种方法是显示一个模态对话框,其中有一个计时器,可以反复检查后台工作人员是否已完成工作(或在您的情况下已完成取消)。后台工作者完成后,模态对话框将控制权返回给您的应用程序。在发生这种情况之前,用户无法与 UI 交互。

Another method (assuming you have a maximum of one modeless window open) is to set ActiveForm.Enabled = false, then loop on Application,DoEvents until the background worker has finished cancelling, after which you can set ActiveForm.Enabled = true again.

另一种方法(假设您最多打开一个无模式窗口)是设置 ActiveForm.Enabled = false,然后在 Application,DoEvents 上循环直到后台工作程序完成取消,之后您可以再次设置 ActiveForm.Enabled = true。

回答by Ian Boyd

Almost all of you are confused by the question, and are not understanding how a worker is used.

几乎所有人都对这个问题感到困惑,并且不了解如何使用工人。

Consider a RunWorkerComplete event handler:

考虑一个 RunWorkerComplete 事件处理程序:

private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (!e.Cancelled)
    {
        rocketOnPad = false;
        label1.Text = "Rocket launch complete.";
    }
    else
    {
        rocketOnPad = true;
        label1.Text = "Rocket launch aborted.";
    }
    worker = null;
}

And all is good.

一切都很好。

Now comes a situation where the caller needs to abort the countdown because they need to execute an emergency self-destruct of the rocket.

现在出现了一种情况,呼叫者需要中止倒计时,因为他们需要对火箭进行紧急自毁。

private void BlowUpRocket()
{
    if (worker != null)
    {
        worker.CancelAsync();
        WaitForWorkerToFinish(worker);
        worker = null;
    }

    StartClaxon();
    SelfDestruct();
}

And there is also a situation where we need to open the access gates to the rocket, but not while doing a countdown:

还有一种情况是我们需要打开火箭的通道门,但不是在进行倒计时:

private void OpenAccessGates()
{
    if (worker != null)
    {
        worker.CancelAsync();
        WaitForWorkerToFinish(worker);
        worker = null;
    }

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

And finally, we need to de-fuel the rocket, but that's not allowed during a countdown:

最后,我们需要为火箭排空燃料,但在倒计时期间是不允许的:

private void DrainRocket()
{
    if (worker != null)
    {
        worker.CancelAsync();
        WaitForWorkerToFinish(worker);
        worker = null;
    }

    if (rocketOnPad)
        OpenFuelValves();
}

Without the ability to wait for a worker to cancel, we must move all three methods to the RunWorkerCompletedEvent:

由于无法等待工作线程取消,我们必须将所有三个方法都移到 RunWorkerCompletedEvent 中:

private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (!e.Cancelled)
    {
        rocketOnPad = false;
        label1.Text = "Rocket launch complete.";
    }
    else
    {
        rocketOnPad = true;
        label1.Text = "Rocket launch aborted.";
    }
    worker = null;

    if (delayedBlowUpRocket)
        BlowUpRocket();
    else if (delayedOpenAccessGates)
        OpenAccessGates();
    else if (delayedDrainRocket)
        DrainRocket();
}

private void BlowUpRocket()
{
    if (worker != null)
    {
        delayedBlowUpRocket = true;
        worker.CancelAsync();
        return;
    }

    StartClaxon();
    SelfDestruct();
}

private void OpenAccessGates()
{
    if (worker != null)
    {
        delayedOpenAccessGates = true;
        worker.CancelAsync();
        return;
    }

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

private void DrainRocket()
{
    if (worker != null)
    {
        delayedDrainRocket = true;
        worker.CancelAsync();
        return;
    }

    if (rocketOnPad)
        OpenFuelValves();
}

Now I could write my code like that, but I'm just not gonna. I don't care, I'm just not.

现在我可以这样写我的代码,但我不会。我不在乎,我只是不在乎。

回答by Ian Boyd

oh man, some of these have gotten ridiculously complex. all you need to do is check the BackgroundWorker.CancellationPending property inside the DoWork handler. you can check it at any time. once it's pending, set e.Cancel = True and bail from the method.

哦,天哪,其中一些已经变得非常复杂。您需要做的就是检查 DoWork 处理程序中的 BackgroundWorker.CancellationPending 属性。您可以随时检查。一旦它挂起,设置 e.Cancel = True 并从方法中保释。

// method here private void Worker_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker bw = (sender as BackgroundWorker);

// 这里的方法 private void Worker_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker bw = (sender as BackgroundWorker);

// do stuff

if(bw.CancellationPending)
{
    e.Cancel = True;
    return;
}

// do other stuff

}

}