.net 如何正确等待BackgroundWorker完成?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1333058/
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-09-03 13:12:51  来源:igfitidea点击:

How to wait correctly until BackgroundWorker completes?

.netmultithreadingbackgroundworker

提问by mark

Observe the following piece of code:

观察下面的一段代码:

var handler = GetTheRightHandler();
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += OnAsyncOperationCompleted;
bw.DoWork += OnDoWorkLoadChildren;
bw.RunWorkerAsync(handler);

Now suppose I want to wait until bwfinishes working. What is the right way to do so?

现在假设我想等到bw完成工作。这样做的正确方法是什么?

My solution is this:

我的解决方案是这样的:

bool finished = false;
var handler = GetTheRightHandler();
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += (sender, args) =>
{
  OnAsyncOperationCompleted(sender, args);
  finished = true;
});
bw.DoWork += OnDoWorkLoadChildren;
bw.RunWorkerAsync(handler);
int timeout = N;
while (!finished && timeout > 0)
{
  Thread.Sleep(1000);
  --timeout;
}
if (!finished)
{
  throw new TimedoutException("bla bla bla");
}

But I do not like it.

但我不喜欢它。

I have considered replacing the finishedflag with a synchronization event, set it in the RunWorkerCompletedhandler and block on it later instead of doing the while-sleep loop.

我已经考虑用finished同步事件替换标志,将它设置在RunWorkerCompleted处理程序中并稍后阻止它,而不是执行 while-sleep 循环。

Alas, it is wrong, because the code may run in the WPF or WindowsForm synchronization context, in which case I would block the same thread as the RunWorkerCompletedhandler runs on, which is clearly not very smart move.

唉,这是错误的,因为代码可能运行在 WPF 或 WindowsForm 同步上下文中,在这种情况下,我会阻塞与RunWorkerCompleted处理程序运行相同的线程,这显然不是很聪明的举动。

I would like to know of a better solution.

我想知道更好的解决方案。

Thanks.

谢谢。

EDIT:

编辑:

P.S.

聚苯乙烯

  • The sample code is so contrived intentionally to clarify my question. I am perfectly aware of the completion callback and yet I want to know how to wait till completion. That is my question.
  • I am aware of Thread.Join, Delegate.BeginInvoke, ThreadPool.QueueUserWorkItem, etc... The question is specifically about BackgroundWorker.
  • 示例代码有意为澄清我的问题而设计。我完全知道完成回调,但我想知道如何等到完成。那是我的问题。
  • 我知道Thread.Join, Delegate.BeginInvoke, ThreadPool.QueueUserWorkItem, 等等...这个问题特别是关于BackgroundWorker.

EDIT 2:

编辑2:

OK, I guess it will be much easier if I explain the scenario.

好吧,我想如果我解释这个场景会容易得多。

I have a unit test method, which invokes some asynchronous code, which in turn ultimately engages a BackgroundWorkerto which I am able to pass a completion handler. All the code is mine, so I can change the implementation if I wish to. I am not going, however, to replace the BackgroundWorker, because it automatically uses the right synchronization context, so that when the code is invoked on a UI thread the completion callback is invoked on the same UI thread, which is very good.

我有一个单元测试方法,它调用一些异步代码,这些代码最终会调用一个BackgroundWorker我能够传递完成处理程序的方法。所有代码都是我的,所以如果我愿意,我可以更改实现。但是,我不打算替换BackgroundWorker,因为它会自动使用正确的同步上下文,因此当在 UI 线程上调用代码时,在同一个 UI 线程上调用完成回调,这非常好。

Anyway, it is possible that the unit test method hits the end before the BW finishes its work, which is not good. So I wish to wait until the BW completes and would like to know the best way for it.

无论如何,单元测试方法有可能在 BW 完成其工作之前就结束了,这并不好。所以我希望等到 BW 完成并想知道最好的方法。

There are more pieces to it, but the overall picture is more or less like I have just described.

它有更多的部分,但整体情况或多或少像我刚刚描述的那样。

回答by JohannesH

Try using the AutoResetEvent class like this:

尝试使用 AutoResetEvent 类,如下所示:

var doneEvent = new AutoResetEvent(false);
var bw = new BackgroundWorker();

bw.DoWork += (sender, e) =>
{
  try
  {
    if (!e.Cancel)
    {
      // Do work
    }
  }
  finally
  {
    doneEvent.Set();
  }
};

bw.RunWorkerAsync();
doneEvent.WaitOne();

Caution:You should make sure that doneEvent.Set()is called no matter what happens. Also you might want to provide the doneEvent.WaitOne()with an argument specifying a timeout period.

注意:doneEvent.Set()无论发生什么,您都应该确保调用它。此外,您可能希望为 提供doneEvent.WaitOne()一个指定超时时间的参数。

Note:This code is pretty much a copy of Fredrik Kalsethanswer to a similar question.

注意:此代码几乎是Fredrik Kalseth类似问题的回答的副本。

回答by Azhar Khorasany

To wait for a background worker thread (single or multiple) do the following:

要等待后台工作线程(单个或多个),请执行以下操作:

  1. Create a List of Background workers you have programatically created:

    private IList<BackgroundWorker> m_WorkersWithData = new List<BackgroundWorker>();
    
  2. Add the background worker in the list:

    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
    worker.WorkerReportsProgress = true;
    m_WorkersWithData.Add(worker);
    worker.RunWorkerAsync();
    
  3. Use the following function to wait for all workers in the List:

    private void CheckAllThreadsHaveFinishedWorking()
    {
        bool hasAllThreadsFinished = false;
        while (!hasAllThreadsFinished)
        {
            hasAllThreadsFinished = (from worker in m_WorkersWithData
                                     where worker.IsBusy
                                     select worker).ToList().Count == 0;
            Application.DoEvents(); //This call is very important if you want to have a progress bar and want to update it
                                    //from the Progress event of the background worker.
            Thread.Sleep(1000);     //This call waits if the loop continues making sure that the CPU time gets freed before
                                    //re-checking.
        }
        m_WorkersWithData.Clear();  //After the loop exits clear the list of all background workers to release memory.
                                    //On the contrary you can also dispose your background workers.
    }
    
  1. 创建您以编程方式创建的后台工作人员列表:

    private IList<BackgroundWorker> m_WorkersWithData = new List<BackgroundWorker>();
    
  2. 在列表中添加后台工作人员:

    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
    worker.WorkerReportsProgress = true;
    m_WorkersWithData.Add(worker);
    worker.RunWorkerAsync();
    
  3. 使用以下函数等待 List 中的所有 worker:

    private void CheckAllThreadsHaveFinishedWorking()
    {
        bool hasAllThreadsFinished = false;
        while (!hasAllThreadsFinished)
        {
            hasAllThreadsFinished = (from worker in m_WorkersWithData
                                     where worker.IsBusy
                                     select worker).ToList().Count == 0;
            Application.DoEvents(); //This call is very important if you want to have a progress bar and want to update it
                                    //from the Progress event of the background worker.
            Thread.Sleep(1000);     //This call waits if the loop continues making sure that the CPU time gets freed before
                                    //re-checking.
        }
        m_WorkersWithData.Clear();  //After the loop exits clear the list of all background workers to release memory.
                                    //On the contrary you can also dispose your background workers.
    }
    

回答by Jeff Wilcox

BackgroundWorker has a completion event. Instead of waiting, call your remaining code path from the completion handler.

BackgroundWorker 有一个完成事件。与其等待,不如从完成处理程序调用剩余的代码路径。

回答by goughy000

This question is old but I don't think the author got the answer he was looking for.

这个问题很老,但我认为作者没有得到他想要的答案。

This is a bit dirty, and it's in vb.NET but works for me

这有点脏,它在 vb.NET 中,但对我有用

Private Sub MultiTaskingForThePoor()
    Try
        'Start background worker
        bgwAsyncTasks.RunWorkerAsync()
        'Do some other stuff here
        For i as integer = 0 to 100
            lblOutput.Text = cstr(i)
        Next

        'Wait for Background worker
        While bgwAsyncTasks.isBusy()
            Windows.Forms.Application.DoEvents()
        End While

        'Voila, we are back in sync
        lblOutput.Text = "Success!"
    Catch ex As Exception
        MsgBox("Oops!" & vbcrlf & ex.Message)
    End Try
End Sub

回答by Don

VB.NET

网络

While BackgroundWorker1.IsBusy()
    Windows.Forms.Application.DoEvents()
End While

You can use this to chain multiple events. (sudo code to follow)

您可以使用它来链接多个事件。(要遵循sudo代码)

download_file("filepath")

    While BackgroundWorker1.IsBusy()
       Windows.Forms.Application.DoEvents()
    End While
'Waits to install until the download is complete and lets other UI events function install_file("filepath")
While BackgroundWorker1.IsBusy()
    Windows.Forms.Application.DoEvents()
End While
'Waits for the install to complete before presenting the message box
msgbox("File Installed")

回答by KevinBui

Checking backgrWorker.IsBusyin the loop with Application.DoEvents()is not a nicely way.

backgrWorker.IsBusy在循环中检查withApplication.DoEvents()不是一个很好的方法。

I agree with @JohannesH, you should definitively use AutoResetEvent as a elegant solution. But not using it in UI Thread, it will cause main thread blocked; it should come from another background worker thread.

我同意@JohannesH,你应该明确地使用 AutoResetEvent 作为一个优雅的解决方案。但是不在UI Thread中使用,会导致主线程阻塞;它应该来自另一个后台工作线程。

AutoResetEvent aevent = new AutoResetEvent(false);    
private void button1_Click(object sender, EventArgs e)
{
    bws = new BackgroundWorker();
    bws.DoWork += new DoWorkEventHandler(bw_work);
    bws.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_complete);
    bws.RunWorkerAsync();

    bwWaiting.DoWork += new DoWorkEventHandler(waiting_work);
    bwWaiting.RunWorkerCompleted += new RunWorkerCompletedEventHandler(waiting_complete);
    bwWaiting.RunWorkerAsync();
}

void bw_work(object sender, DoWorkEventArgs e)
{
    Thread.Sleep(2000);
}

void bw_complete(object sender, RunWorkerCompletedEventArgs e)
{
    Debug.WriteLine("complete " + bwThread.ToString());
    aevent.Set();
}
void waiting_work(object sender, DoWorkEventArgs e)
{
    aevent.WaitOne();
}

void waiting_complete(object sender, RunWorkerCompletedEventArgs e)
{
    Debug.WriteLine("complete waiting thread");
}

回答by Beefjeff

I used Taskswith a BackgroundWorker

我将TasksBackgroundWorker 一起使用

You can create any number of tasks and add them to a list of tasks. The worker will start when a task is added, restart if a task is added while the worker IsBusy, and stop once there are no more tasks.

您可以创建任意数量的任务并将它们添加到任务列表中。worker 会在添加任务时启动,如果在 worker 忙时添加了任务,则重新启动,并在没有更多任务时停止。

This will allow you to update the GUI asynchronously as much as you need to without freezing it.

这将允许您根据需要异步更新 GUI,而不会冻结它。

This works as is for me.

这对我来说是有效的。

    // 'tasks' is simply List<Task> that includes events for adding objects
    private ObservableCollection<Task> tasks = new ObservableCollection<Task>();
    // this will asynchronously iterate through the list of tasks 
    private BackgroundWorker task_worker = new BackgroundWorker();

    public Form1()
    {
        InitializeComponent();
        // set up the event handlers
        tasks.CollectionChanged += tasks_CollectionChanged;
        task_worker.DoWork += task_worker_DoWork;
        task_worker.RunWorkerCompleted += task_worker_RunWorkerCompleted;
        task_worker.WorkerSupportsCancellation = true;

    }

    // ----------- worker events
    void task_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (tasks.Count != 0)
        {
            task_worker.RunWorkerAsync();
        }
    }

    void task_worker_DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {

            foreach (Task t in tasks)
            {
                t.RunSynchronously();
                tasks.Remove(t);
            }
        }
        catch
        {
            task_worker.CancelAsync();
        }
    }


    // ------------- task event
    // runs when a task is added to the list
    void tasks_CollectionChanged(object sender,
        System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (!task_worker.IsBusy)
        {
            task_worker.RunWorkerAsync();
        }
    }

Now all you need is to create a new Task and add it to the List<>. It will be run by the worker in the order it was placed into the List<>

现在您只需要创建一个新任务并将其添加到 List<> 中。它将由工作人员按照放入 List<> 的顺序运行

Task t = new Task(() => {

        // do something here
    });

    tasks.Add(t);

回答by be981

I was also looking for a suitable solution. I solved the waiting with an exclusive lock. The critical path in code are writing to a public container (here the console) and increasing or decreasing the workers. No thread should interfere while writing to this variable, otherwise the count is not guaranteed anymore.

我也在寻找合适的解决方案。我用排他锁解决了等待。代码中的关键路径是写入公共容器(此处为控制台)并增加或减少工作人员。写入此变量时不应有线程干扰,否则不再保证计数。

public class Program
{
    public static int worker = 0;
    public static object lockObject = 0;

    static void Main(string[] args)
    {

        BackgroundworkerTest backgroundworkerTest = new BackgroundworkerTest();
        backgroundworkerTest.WalkDir("C:\");
        while (backgroundworkerTest.Worker > 0)
        {
            // Exclusive write on console
            lock (backgroundworkerTest.ExclusiveLock)
            {
                Console.CursorTop = 4; Console.CursorLeft = 1;
                var consoleOut = string.Format("Worker busy count={0}", backgroundworkerTest.Worker);
                Console.Write("{0}{1}", consoleOut, new string(' ', Console.WindowWidth-consoleOut.Length));
            }
        }
    }
}

public class BackgroundworkerTest
{
    private int worker = 0;
    public object ExclusiveLock = 0;

    public int Worker
    {
        get { return this.worker; }
    }

    public void WalkDir(string dir)
    {
        // Exclusive write on console
        lock (this.ExclusiveLock)
        {
            Console.CursorTop = 1; Console.CursorLeft = 1;
            var consoleOut = string.Format("Directory={0}", dir);
            Console.Write("{0}{1}", consoleOut, new string(' ', Console.WindowWidth*3 - consoleOut.Length));
        }

        var currentDir = new System.IO.DirectoryInfo(dir);
        DirectoryInfo[] directoryList = null;
        try
        {
            directoryList = currentDir.GetDirectories();
        }
        catch (UnauthorizedAccessException unauthorizedAccessException)
        {
            // No access to this directory, so let's leave
            return;
        }

        foreach (var directoryInfo in directoryList)
        {
            var bw = new BackgroundWorker();

            bw.RunWorkerCompleted += (sender, args) =>
            {
                // Make sure that this worker variable is not messed up
                lock (this.ExclusiveLock)
                {
                    worker--;
                }
            };

            DirectoryInfo info = directoryInfo;
            bw.DoWork += (sender, args) => this.WalkDir(info.FullName);

            lock (this.ExclusiveLock)
            {
                // Make sure that this worker variable is not messed up
                worker++;
            }
            bw.RunWorkerAsync();
        }
    }
}

回答by RvdK

not quite sure what u mean by waiting. Do you mean that you want something done (by the BW) after thats done you want to do something else? Use bw.RunWorkerCompleted like you do (use a seperate function for readability) and in that callback function do you next stuff. Start a timer to check if the work doesnt take too long.

不太确定你说的等待是什么意思。你的意思是你想要做一些事情(由 BW 完成)之后你想做其他事情吗?像您一样使用 bw.RunWorkerCompleted(使用单独的函数来提高可读性),然后在该回调函数中执行下一步操作。启动计时器以检查工作是否花费太长时间。

var handler = GetTheRightHandler();
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += (sender, args) =>
{
  OnAsyncOperationCompleted(sender, args);
});
bw.DoWork += OnDoWorkLoadChildren;
bw.RunWorkerAsync(handler);

Timer Clock=new Timer();
Clock.Interval=1000;
Clock.Start();
Clock.Tick+=new EventHandler(Timer_Tick);

public void Timer_Tick(object sender,EventArgs eArgs)
{   
    if (bw.WorkerSupportsCancellation == true)
    {
        bw.CancelAsync();
    }

    throw new TimedoutException("bla bla bla");
 }

In the OnDoWorkLoadChildren:

在 OnDoWorkLoadChildren 中:

if ((worker.CancellationPending == true))
{
    e.Cancel = true;
    //return or something
}

回答by csc

In OpenCV exists function WaitKey. Ir allows solve this issue in that way:

在 OpenCV 中存在函数 WaitKey。Ir 允许以这种方式解决这个问题:

while (this->backgroundWorker1->IsBusy) {
    waitKey(10);
    std::cout << "Wait for background process: " << std::endl;
}
this->backgroundWorker1->RunWorkerAsync();