发布版和调试版的区别是什么?

6

当我将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,_bgDoWork 程序将继续执行。
 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时,尽管我可以从日志中看到iswaiting已经被设置为false,但我永远无法跳出while(iswaiting)循环。 编辑: 欢迎更好的方法!

哎呀,你可能应该考虑另一种实现方式,否则 while 循环可能会占用大量 CPU 资源。 - Bob Vale
我知道这不是问题所在,但你为什么要等呢?你可以在进度改变方法中执行你等待的代码。或者你甚至可以在BackgroundWorkerCompleted事件中完成它。 - Benjamin Danger Johnson
1
从他的代码看,似乎他正在等待用户指示它是应该继续运行还是提前停止,因此将 DoWork 的其余部分移动到 ProgressChangedCompleted 可能不是正确的解决方案。 - Servy
@Servy 或许是这样,但如果他检查对话框的结果已经改变,他可以简单地进行异步取消。如果他不能允许取消,则在此情况下使用后台工作器并不是最佳解决方案。 - Benjamin Danger Johnson
1
@BenjaminDangerJohnson 尝试取消的问题在于您不知道从弹出窗口到注意到取消时BGW还会处理多少。如果可以接受“额外”工作,那么没问题,但如果不能,则仍然需要等待的问题。 - Servy
显示剩余2条评论
3个回答

6

这可能是由于线程优化造成的。为了在发布模式下安全地“看到”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,而且你也不必自己担心内存屏障。

非常感谢!ManualResetEvent效果很好!只有一个问题,每次使用allowProgress.WaitOne();之前我需要设置'allowProgress = new ManualResetEvent(false);'。我不能在allowProgress.Set();之后重复使用allowProgress.WaitOne();吗? - Bolu
1
@Bolu 你可以在 allowProgress 上调用 Reset 方法,使其再次“阻塞”:http://msdn.microsoft.com/zh-cn/library/system.threading.eventwaithandle.reset.aspx。另外,还有一个 AutoResetEvent 类(根据情况,它可能更适合)。 - Reed Copsey
我已经尝试过它们,可以使用ResetAutoResetEvent来解决!非常感谢! - Bolu

5
所以您的问题可能是使用了布尔字段,并且您没有将其标记为volatile。因此,某些优化(通常仅在发布模式下应用)可能导致两个线程都访问本地于其线程的副本字段(例如,可能位于处理器的缓存上)。但是,在这里标记字段volatile并不是一个好主意。您有一个更根本的问题,即执行自旋等待实际上几乎总是不明智的。您应该使用一种真正暂停线程直到继续的方法。一种方法是使用ManualResetEventSemaphore。通过查看您的代码,您正在等待用户关闭在进度更改事件中触发的消息框。我会建议您将其包含在实际的“执行工作”事件中,而不是在进度更改事件中。对于已触发的进度更改事件,希望doWork方法不再关心它。

我最终通过使用Application.Current.Dispatcher.Invoke来显示MessageBox,将用户交互部分直接移动到DoWork事件中。我的做法正确吗? - Bolu
1
@Bolu 嗯,如果您不利用BGW将事件调度到UI线程,则会破坏其设计目的,但您所做的并没有什么不对。这肯定比您在问题代码中所做的要好得多。 - Servy

0
一个更好的方法是使用 Semaphore 信号,以便您可以继续进行。
private Semaphore semaphore = new Semaphore(1, 1000);
semaphore.WaitOne();

然后当你想要发布时

semaphore.Release();

为什么信号量是更好的选择? - user191152
这是比bool更好的方法,非常易于使用,最终也是保证。 - faragh47

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接