使用while循环取消后台工作人员

本文关键字:后台 工作人员 取消后 取消 while 循环 使用 | 更新日期: 2023-09-27 18:25:40

我知道使用事件等待句柄取消后台工作程序的常见方法。。。但我想知道,使用while循环来诱捕和暂停后台工作人员的工作是正确的吗?我这样编码:

    Bool stop = false;
    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        progressBar1.Minimum = 0;
        progressBar1.Maximum = 100000;
        progressBar1.Value = 0;
        for (int i = 0; i < 100000; i++)
        {
          progressBar1.Value++;
            if (i == 50000)
                stop = true;
            while (stop)
            { }
        }
    }
    private void button1_Click(object sender, EventArgs e)
    {
        stop = !stop;
    }

使用while循环取消后台工作人员

你试过了吗?发生了什么?这是你想要的吗?你有没有注意到你电脑的风扇在加速,在一个"无所事事"的循环中处理来自CPU的所有热量?

事实是,你一开始就不应该"暂停"后台任务;如果你不想让它继续运行,就中断它。如果你想稍后恢复,就提供一种允许这样做的机制。即使在WaitHandle对象上有效地阻止线程等待也是错误的,因为这会浪费线程池线程。

你在这里发布的代码是关于实现"暂停"的最糟糕的方法。您不需要等待某些同步对象(如WaitHandle),而是让当前线程在不中断的情况下循环,不断检查标志的值。即使忽略了是否使用volatile的问题(代码示例没有显示这一点,但它也不会编译,所以…),强迫CPU内核做这么多工作却一无所获也是非常可怕的。

首先不要暂停BackgroundWorker.DoWork处理程序。真正地只是不要那样做。但如果你坚持,那么至少使用某种可等待对象,而不是像你在这里发布的例子中那样使用"旋转等待"循环。


以下是一个示例,说明如果您希望避免在"暂停"时完全占用线程,代码可能如何工作。首先,不要使用BackgroundWorker,因为它没有一种优雅的方式来做到这一点。第二,一定要使用await…这具体实现了您想要的:它允许当前方法返回,但不会丢失对其进度的跟踪。当该方法等待的东西指示完成时,该方法将继续执行。

在下面的示例中,我试图猜测调用RunWorkerAsync()的代码是什么样子的。或者更确切地说,我只是假设你有一个button2,当点击它时,你会调用该方法来启动你的工人任务。如果这还不足以让你朝着正确的方向前进,请通过包含一个好的最小完整代码示例来改进你的问题,展示你实际在做什么。

// These fields will work together to provide a way for the thread to interrupt
// itself temporarily without actually using a thread at all.
private TaskCompletionSource<object> _pause;
private readonly object _pauseLock = new object();
private void button2_Click(object sender, DoWorkEventArgs e)
{
    // Initialize ProgressBar. Note: in your version of the code, this was
    // done in the DoWork event handler, but that handler isn't executed in
    // the UI thread, and so accessing a UI object like progressBar1 is not
    // a good idea. If you got away with it, you were lucky.
    progressBar1.Minimum = 0;
    progressBar1.Maximum = 100000;
    progressBar1.Value = 0;
    // This object will perform the duty of the BackgroundWorker's
    // ProgressChanged event and ReportProgress() method.
    Progress<int> progress = new Progress<int>(i => progressBar1.Value++);
    // We do want the code to run in the background. Use Task.Run() to accomplish that
    Task.Run(async () =>
    {
        for (int i = 0; i < 100000; i++)
        {
            progress.Report(i);
            Task task = null;
            // Locking ensures that the two threads which may be interacting
            // with the _pause object do not interfere with each other.
            lock (_pauseLock)
            {
                if (i == 50000)
                {
                    // We want to pause. But it's possible we lost the race with
                    // the user, who also just pressed the pause button. So
                    // only allocate a new TCS if there isn't already one
                    if (_pause == null)
                    {
                        _pause = new TaskCompletionSource<object>();
                    }
                }
                // If by the time we get here, there's a TCS to wait on, then
                // set our local variable for the Task to wait on. In this way
                // we resolve any other race that might occur between the time
                // we checked the _pause object and then later tried to wait on it
                if (_pause != null)
                {
                    task = _pause.Task;
                }
            }
            if (task != null)
            {
                // This is the most important part: using "await" tells the method to
                // return, but in a way that will allow execution to resume later.
                // That is, when the TCS's Task transitions to the completed state,
                // this method will resume executing, using any available thread
                // in the thread pool.
                await task;
                // Once we resume execution here, reset the TCS, to allow the pause
                // to go back to pausing again.
                lock (_pauseLock)
                {
                    _pause.Dispose();
                    _pause = null;
                }
            }
        }
    });
}
private void button1_Click(object sender, EventArgs e)
{
    lock (_pauseLock)
    {
        // A bit more complicated than toggling a flag, granted. But it achieves
        // the desirable goal.
        if (_pause == null)
        {
            // Creates the object to wait on. The worker thread will look for
            // this and wait if it exists.
            _pause = new TaskCompletionSource<object>();
        }
        else if (!_pause.Task.IsCompleted)
        {
            // Giving the TCS a result causes its corresponding Task to transition
            // to the completed state, releasing any code that might be waiting
            // on it.
            _pause.SetResult(null);
        }
    }
}

请注意,上面的内容和您最初的示例一样是人为的。如果你真正拥有的只是一个简单的单循环变量,从0到100000迭代,并在中途停止,那么就不需要像上面这样复杂的东西了。您只需将循环变量存储在某个数据结构中,退出正在运行的任务线程,然后当您想继续时,传入当前循环变量值,以便方法可以在正确的索引处继续。

但我假设你的真实世界的例子并不是那么简单。上述策略将适用于任何状态处理,编译器将为您完成存储中间状态的所有繁重工作。