使用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;
}
你试过了吗?发生了什么?这是你想要的吗?你有没有注意到你电脑的风扇在加速,在一个"无所事事"的循环中处理来自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迭代,并在中途停止,那么就不需要像上面这样复杂的东西了。您只需将循环变量存储在某个数据结构中,退出正在运行的任务线程,然后当您想继续时,传入当前循环变量值,以便方法可以在正确的索引处继续。
但我假设你的真实世界的例子并不是那么简单。上述策略将适用于任何状态处理,编译器将为您完成存储中间状态的所有繁重工作。