使用 Dispatcher.Invoke 会使我的线程安全吗?
在我的 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 之后,所以我不会在那里遇到问题。
或者我是否需要一把锁才能安全,a:
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;
}
}
}
In my WPF app I have a long running upload running, which raises progress events as it goes which updates a progress bar. The user also has a chance of cancelling the upload, or it might go wrong. These are all async events, so they need to be executed using Dispatcher.Invoke in order to update the UI.
So the code looks like this, 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;
}
}
Supposing that setting view.Progress on a disposed view is going to throw an error, is this code thread safe? i.e. if a user clicks cancel while the progress is updating, he/she will have to wait until the progress has been updated, and if the progress is updated during execution of OnCancelButtonClicked, the Dispatcher.Invoke call will cause the view.Progress update to be queued til after _cancelled is set, so I won't get a problem there.
Or do I need a lock to be safe, a 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;
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您不必添加锁。 Dispatcher.Invoke 和 BeginInvoke 请求不会在其他代码中间运行(这就是它们的全部要点)。
只需考虑两件事:
编辑:首先,我没有引用,因为不幸的是,有关该主题的 MSDN 页面的细节很少 - 但我编写了测试程序来检查 BeginInvoke 的行为,我在这里写的所有内容都是这些测试的结果。
现在,为了扩展第二点,我们首先需要了解调度程序的作用。显然这是一个非常简单的解释。
任何 Windows UI 都是通过处理消息来工作的;例如,当用户将鼠标移动到窗口上时,系统将向该窗口发送 WM_MOUSEMOVE 消息。
系统通过添加一个队列来发送消息,每个线程都可以有一个队列,同一个线程创建的所有窗口共享同一个队列。
在每个 Windows 程序的核心都有一个称为“消息循环”或“消息泵”的循环,该循环从队列中读取下一条消息并调用适当的窗口代码来处理该消息。
在 WPF 中,此循环以及所有相关处理均由 Dispatcher 处理。
应用程序可以处于消息循环中等待下一条消息,也可以正在执行某些操作。这就是为什么当您进行长时间计算时,所有线程的窗口都会变得无响应 - 线程正忙于工作并且不会返回到消息循环来处理下一条消息。
Dispatcher.Invoke 和 BeginInvoke 的工作原理是对请求的操作进行排队,并在线程下次返回消息循环时执行它。
这就是为什么 Dispatcher.(Begin)Invoke 无法在方法中间“注入”代码,在方法返回之前您不会返回消息循环。
但是
任何代码都可以运行消息循环。当您调用运行消息循环的任何内容时,将调用调度程序并可以运行 (Begin)Invoke 操作。
什么样的代码有消息循环?
因此,总结一下:
You don't have to add a lock. Dispatcher.Invoke and BeginInvoke requests will not run in the middle of other code (that's the whole point of them).
Just two things to consider:
EDIT: first, I don't have citations because the MSDN pages on the subject are unfortunately very low on details - but I have written test programs to check the behavior of BeginInvoke and everything I write here is the result of those tests.
Now, to expand on the second point we first need to understand what the dispatcher does. Obviously this is a very simplified explanation.
Any Windows UI works by processing messages; For example when the user moves the mouse over a window the system will send that window a WM_MOUSEMOVE message.
The system send the message by adding it a queue, each thread may have a queue, all windows created by the same thread share the same queue.
In the heart of every Windows program there's a loop called "message loop" or "message pump", this loop reads the next message from the queue and calls the appropriate window's code to process that message.
In WPF this loop and all the related processing handled by the Dispatcher.
An application can either be in the message loop waiting for the next message or it could be doing something. That is why when you have a long calculation all the thread's windows become unresponsive - the thread is busy working and doesn't return to the message loop to process the next message.
Dispatcher.Invoke and BeginInvoke works by queuing the requested operation and executing it the next time the thread returns to the message loop.
That is why Dispatcher.(Begin)Invoke can't "inject" code in the middle of your method, you won't get back to the message loop until your method returns.
BUT
Any code can run a message loop. When you call anything that runs a message loop the Dispatcher will be called and can run the (Begin)Invoke operations.
What kinds of code has a message loop?
So, to summarize:
这是一个有趣的问题。 在调度程序中执行的项目会排队并在与 UI 交互相同的线程上执行。这是关于这个主题的最佳文章:
http://msdn.microsoft.com/en-us/library/ms741870。 aspx
如果我大胆猜测,我会说 Dispatcher.Invoke(Action) 可能会排队一个原子工作项,所以它可能会没问题,但我不会确定它是否将您的 UI 事件处理程序包装在原子操作项中,例如:
为了安全起见,我会亲自锁定,但您的问题需要更多研究。可能需要深入反射镜才能确定。
顺便说一句,我可能会稍微优化一下你的锁。
但这可能有点矫枉过正了:)
This is an interesting question. Items executed in the dispatcher are queued and execute on the same thread as UI interaction. Here's the best article on this subject:
http://msdn.microsoft.com/en-us/library/ms741870.aspx
If I were to venture a guess, I would say that Dispatcher.Invoke(Action) probably queues an atomic work item, so it's likely going to be ok, however I'm not sure if it wraps up your UI event handler in an atomic action item for instance:
For safety's sake I would personally lock, but your question warrants more research. Might need a dive in reflector to figure out for sure.
As an aside, I'd probably optimize your lock a bit.
But that's probably overkill :)