如何使用InvokeRequired和Invoke让我们的应用程序线程安全。
让我们考虑一下这样的代码:
private void ThreadSafeUpdate(string message)
{
if (this.textBoxSome.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(msg);
this.Invoke
(d, new object[] { message });
}
else
{
// It's on the same thread, no need for Invoke
this.textBoxSome.Text = message;
}
}
是否可以在InvokeRequired
之后和InvokeRequired
之前更改Invoke
的状态?如果没有,那么为什么?
如何调用使线程安全?
如果InvokeRequired说明是当前线程拥有控件,那么线程如何知道它是或者它不是所有者。
我们假设SomeMethod()当前正在Thread1上运行。我们想从Thread2调用它。在内部,此方法更新某些字段。 Method.Invoke内部是否包含某种锁机制?
如果SomeMethod()需要很长时间并且我们想在控件所有者线程上运行其他内容,该怎么办?调用锁定所有者线程还是某种后台线程安全任务?
ThreadSafeUpdate() //takes 5 minutes in Thread2
ThreadSafeUpdate() //after 2 minutes, we are running it in other thread2
ThreadSafeUpdate() //next run from Thread3
我认为这是一种可以在winforms之外实现的一般模式,它的名字是什么?
答案 0 :(得分:3)
是否可以更改InvokeRequired的状态
是的,这是很常见的事情。要么是因为你在表单的Load事件被触发之前太快启动了线程。或者因为用户在此代码运行时关闭了窗口。在这两种情况下,此代码都会因异常而失败。当线程在窗口创建之前竞争时,InvokeRequired失败,当UI线程在线程之前竞争时,调用的代码失败。在测试代码时,异常的几率很低,太低而无法诊断错误。
如何调用使其线程安全?
使用此代码无法使其安全,这是一项基本竞赛。必须通过将窗口关闭与线程执行联锁来使其安全。在允许窗口关闭之前,必须确保线程已停止。 this answer的主题。
他怎么知道他或他不是老板。
这可以通过winapi调用GetWindowsThreadProcessId()发现。 Handle 属性是它的基本oracle。相当不错的测试,但有一个明显的缺陷,当Handle不再有效时,它无法工作。一般使用oracle是不明智的,你应该总是知道代码在工作线程上运行的时间。这样的代码与在UI线程上运行的代码有根本的不同。它代码很慢。
我们想从Thread2
调用它
这通常不可能。将来自一个线程的调用封送到特定的其他线程需要其他线程进行协作。它必须解决producer-consumer problem。看看链接,该问题的根本解决方案是调度程序循环。您可能认识到,这是程序的UI线程如何运作的。哪个必须解决此问题,它从任意其他线程获取通知,UI从不是线程安全的。但是工作线程一般不会尝试自己解决这个问题,除非你明确地写它,你需要一个线程安全的队列和一个清空它的循环。
如果SomeMethod()需要很长时间
,那该怎么办?
不确定我是否遵循,使用线程的目的是让代码花费很长时间不做任何事情来损害用户界面的响应能力。
我认为这是某种一般模式
有,它看起来不像这样。这种代码往往是在你有一个拍摄时刻并且发现你的用户界面很冷的时候写的。在从未设计用于支持线程的代码之上的螺栓线程永远是一个坏主意。你会忽略太多讨厌的小细节。非常重要的是最小化工作线程与UI线程交互的次数,您的代码正好相反。使用BackgroundWorker类陷入成功之中,其RunWorkerCompleted事件提供了一种良好的同步方式,可以使用后台操作的结果更新UI。如果你喜欢Tasks,那么TaskScheduler.FromCurrentSynchronizationContext()方法可以帮助你本地化交互。
答案 1 :(得分:2)
await
检查和InvokeRequired
调用之间使用Invoke
而未捕获执行上下文,则可能会发生这种情况。当然,如果您已经在使用await
,那么您可能不会使用Invoke
和InvokeRequired
。
编辑:我刚刚注意到,当尚未创建控件句柄时,InvokeRequired
将返回false
。它应该没什么区别,因为当控件尚未完全创建时,你的调用将无论如何都会失败,但需要牢记这一点。GetWindowThreadProcessId
- 操作系统跟踪控件(更重要的是,它们的消息循环)。Invoke
无法返回。 Invoke
本身只将命令发布到队列并等待处理。但该命令在GUI线程上运行,而不是Invoke
- 调用者。因此,您的示例中的SomeMethod
调用将被序列化,Invoke
调用本身将等到第二个调用完成。然而,所有这些只是对工作的简单解释。事实是,你不应该真的需要InvokeRequired
......好吧,永远。这是一个不同年龄的神器。这实际上主要是关于少量订单的杂乱线程,这不是一个好的做法。我看到的用途是延迟编码或遗留代码的修补程序 - 在新代码中使用它是愚蠢的。使用InvokeRequired
的论点通常就像“它允许我们安全地处理这个业务逻辑,无论它是否在GUI线程中运行”。希望你可以看到这个逻辑的问题:)
此外,它不是免费线程安全。它确实引入了延迟(特别是当GUI线程也在做一些非GUI的工作时 - 很可能在首先使用InvokeRequired
的代码中)。它不会保护您免受其他线程对共享状态的访问。它可以引入死锁。甚至不要让我开始使用Application.DoEvents
的代码做任何事情。
当然,一旦考虑await
,它就没那么有用了 - 编写异步代码非常容易,并且它允许您确保GUI代码始终在GUI上下文中运行,其余的可以在任何你想要的地方运行(如果它根本使用一个线程)。