用while循环取消backgroundworker

时间:2015-12-04 07:22:28

标签: c# multithreading thread-safety progress-bar backgroundworker

我知道使用eventwaithandles取消后台工作人员的常用方法...... 但我想知道是否有权使用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;
    }

1 个答案:

答案 0 :(得分:1)

你尝试过吗?发生了什么?这是你想要发生的事吗?您是否注意到计算机的风扇加速,以紧密的“无操作”循环处理CPU的所有热量?

事实上,你不应该首先“暂停”后台任务;如果你不继续运行,请打断它。如果您希望以后能够恢复,请提供允许的机制。即使让你的线程有效地阻塞等待WaitHandle对象也是错误的,因为它浪费了一个线程池线程。

您在此处发布的代码是关于实施“暂停”的最糟糕方式。您不必等待某个同步对象(例如WaitHandle),而是让当前线程在不中断的情况下循环,不断检查标志的值。甚至忽略了你是否正在使用volatile的问题(代码示例没有显示,但它也不会编译,所以...),强迫一个可怕的 CPU核心做了很多工作但却无处可去。

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


下面是一个示例,说明如果您希望在“暂停”时完全占用线程,则代码可能如何工作。首先,不要使用BackgroundWorker,因为它没有优雅的方法来执行此操作。其次,请使用await ...具体执行您想要的操作:它允许当前方法返回,但不会失去对其进度的跟踪。当等待的东西表示完成时,该方法将继续执行。

在下面的示例中,我试图猜测调用RunWorkerAsync()的代码是什么样的。或者更确切地说,我假设你有一个button2,点击它时你会调用该方法来启动你的工作任务。如果这还不足以让您指出正确的方向,请通过包含a good, minimal, complete code example显示您实际所做的事情来改进您的问题。

// 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到100,000迭代并且中途停止,那么就不需要像上面那样复杂了。您只需将循环变量存储在某个数据结构中,退出正在运行的任务线程,然后当您想要恢复时,传入当前循环变量值,以便该方法可以在正确的索引处恢复。

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