在我的WPF应用程序中,我有一个长时间运行的上传,它会引发进度事件,因为它会更新进度条。用户还有机会取消上传,否则可能会出错。这些都是异步事件,因此需要使用Dispatcher.Invoke执行它们以更新UI。
所以代码看起来像这样,ish:
void OnCancelButtonClicked(object sender, EventArgs e)
{
upload.Cancel();
_cancelled = true;
view.Close();
view.Dispose();
}
void OnProgressReceived(object sender, EventArgs<double> e)
{
Dispatcher.Invoke(() =>
{
if (!cancelled)
view.Progress = e.Value;
}
}
假设在已调配视图上设置view.Progress将抛出错误,此代码线程是否安全?即,如果用户在进度更新时单击取消,则他/她将不得不等待进度更新,如果在执行OnCancelButtonClicked期间更新进度,则Dispatcher.Invoke调用将导致view.Progress更新到在_cancelled设置后排队,所以我不会在那里遇到问题。
或者我需要锁才能安全,la:
object myLock = new object();
void OnCancelButtonClicked(object sender, EventArgs e)
{
lock(myLock)
{
upload.Cancel();
_cancelled = true;
view.Close();
view.Dispose();
}
}
void OnProgressReceived(object sender, EventArgs<double> e)
{
Dispatcher.Invoke(() =>
{
lock(myLock)
{
if (!cancelled)
view.Progress = e.Value;
}
}
}
答案 0 :(得分:8)
您不必添加锁。 Dispatcher.Invoke和BeginInvoke请求不会在其他代码的中间运行(这就是它们的全部内容)。
只需要考虑两件事:
现在,为了扩展第二点,我们首先需要了解调度员的作用。显然这是一个非常简化的解释。
任何Windows UI都通过处理消息来工作;例如,当用户将鼠标移到窗口上时,系统将向该窗口发送WM_MOUSEMOVE消息。
系统通过添加队列发送消息,每个线程可能有一个队列,同一个线程创建的所有窗口共享同一个队列。
在每个Windows程序的核心都有一个名为“消息循环”或“消息泵”的循环,该循环从队列中读取下一条消息并调用相应窗口的代码来处理该消息。
在WPF中,此循环以及Dispatcher处理的所有相关处理。
应用程序可以在消息循环中等待下一条消息,也可以执行某些操作。这就是为什么当你有一个很长的计算时,所有线程的窗口都没有响应 - 线程正忙着工作,并没有返回消息循环来处理下一条消息。
Dispatcher.Invoke和BeginInvoke通过对请求的操作进行排队并在下次线程返回消息循环时执行它来工作。
这就是为什么Dispatcher。(Begin)Invoke不能在你的方法中间“注入”代码,在方法返回之前你不会回到消息循环。
BUT
任何代码都可以运行消息循环。当你调用任何运行消息循环的东西时,将调用Dispatcher并运行(Begin)Invoke操作。
什么类型的代码有消息循环?
所以,总结一下:
答案 1 :(得分:0)
这是一个有趣的问题。 在调度程序中执行的项目在与UI交互相同的线程上排队并执行。这是关于这个主题的最好的文章: http://msdn.microsoft.com/en-us/library/ms741870.aspx
如果我冒险猜测,我会说 Dispatcher.Invoke(Action)可能会将一个原子工作项排队,所以它可能会好起来,但我不确定是否它将您的UI事件处理程序包装在原子操作项中,例如:
//Are these bits atomic? Not sure.
upload.Cancel();
_cancelled = true;
为了安全起见,我会亲自锁定,但你的问题需要更多的研究。可能需要在反射器中潜水才能确定。
顺便说一句,我可能会优化你的锁定。
Dispatcher.Invoke(() =>
{
if (!cancelled)
{
lock(myLock)
{
if(!cancelled)
view.Progress = e.Value;
}
}
}
但那可能有点过分了:))