你試過嗎?發生了什麼?這是你想要發生的事情嗎?你有沒有注意到你的電腦風扇正在加速,在緊張的「無所事事」循環中處理來自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重複,並且在一半的時間內停下來,沒有什麼比上面所要求的那麼複雜。您只需將循環變量存儲在某個數據結構中,退出正在運行的任務線程,然後在想要恢復時,傳入當前循環變量值,以便方法可以從正確的索引繼續。
但我假設你的真實世界的例子並不那麼簡單。並且上述策略將適用於任何有狀態處理,編譯器會爲您完成所有重要的存儲中間狀態。
沒有它不正確的方式。 –