当我将Visual Studio 2010配置从Debug更改为Release时,我得到了一个非常奇怪的行为:
我有一个BackgroundWorker
:_bg
,在DoWork
中我有:
iswaiting = true;
_bg.ReportProgress(1, filePath);
while (iswaiting)
{
;
}
//My other part of code (EDIT: something do to with the `result` I get from the user.)
在ProgressChanged
中,我有一个MessageBox
,并且在用户交互之后,iswaiting
将被设置回false,并且_bg
DoWork
程序将继续。
void _bg_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//my other part of code........
result = Microsoft.Windows.Controls.MessageBox.Show("Question" ,"Title", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);
iswaiting=false;
log(iswaiting.toString());
}
当我从Visual Studio运行它或在Debug模式下构建它时,所有这些都能很好地工作,但当我将它构建到Release时,我永远不会退出while(iswaiting)
循环,尽管我可以从日志中看到iswaiting
已经设置回false
。
编辑:
更好的方法非常受欢迎
这可能是由于线程优化。为了在发布模式下安全地"看到"iswaiting
的变化,您需要一个适当的内存屏障。
最简单的"修复"方法是将iswaiting
标记为volatile
:
volatile bool iswaiting;
话虽如此,像这样"旋转"将完全消耗一个CPU核心。一个更好的方法是使用ManualResetEvent
来表示您可以继续。
// Add:
private ManualResetEvent allowProgress = new ManualResetEvent(false);
然后,不使用iswaiting,而是使用:
_bg.ReportProgress(1, filePath);
allowProgress.WaitOne(); // This will block until it's set
要允许这种情况继续,请使用:
result = Microsoft.Windows.Controls.MessageBox.Show("Question" ,"Title", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);
allowProgress.Set();
这里的优点是,当你被阻塞时,你不会消耗CPU,而且你自己也不必担心内存障碍。
所以您的问题很可能是您使用了一个布尔字段,而您还没有将其标记为volatile
。因此,某些优化(通常仅在发布模式下应用)可能导致两个线程都访问其线程本地的字段副本(例如,可能在其处理器核心的缓存上)。
然而,在这里标记字段volatile
并不是一个好主意。你有一个更根本的问题,那就是你在执行spinwait,这实际上总是一个坏主意。您应该使用一个方法来暂停线程,直到它应该继续。一种方法是使用CCD_ 18或CCD_。
查看您的代码,您正在等待的是用户取消进度更改事件中触发的消息框。我想说的是,与其在进度更改事件中包含这一点,不如简单地将其包含在实际的"做工作"事件中。doWork方法最好在触发进度更改事件后不关心该事件。
更好的方法是使用可以继续的信号量信号。
private Semaphore semaphore = new Semaphore(1, 1000);
semaphore.WaitOne();
在你想发布之后
semaphore.Release();