如何调用InvokeRequired和Invoke让我们使应用程序线程安全

时间:2015-01-27 12:26:03

标签: c# multithreading

如何使用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;
        }
    }
  1. 是否可以在InvokeRequired之后和InvokeRequired之前更改Invoke的状态?如果没有,那么为什么?

  2. 如何调用使线程安全?

  3. 如果InvokeRequired说明是当前线程拥有控件,那么线程如何知道它是或者它不是所有者。

  4. 我们假设SomeMethod()当前正在Thread1上运行。我们想从Thread2调用它。在内部,此方法更新某些字段。 Method.Invoke内部是否包含某种锁机制?

  5. 如果SomeMethod()需要很长时间并且我们想在控件所有者线程上运行其他内容,该怎么办?调用锁定所有者线程还是某种后台线程安全任务?

    ThreadSafeUpdate() //takes 5 minutes in Thread2
    ThreadSafeUpdate() //after 2 minutes, we are running it in other thread2
    ThreadSafeUpdate() //next run from Thread3
    
  6. 我认为这是一种可以在winforms之外实现的一般模式,它的名字是什么?

2 个答案:

答案 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)

  1. 通常,没有。但是,如果您在await检查和InvokeRequired调用之间使用Invoke而未捕获执行上下文,则可能会发生这种情况。当然,如果您已经在使用await,那么您可能不会使用InvokeInvokeRequired编辑:我刚刚注意到,当尚未创建控件句柄时,InvokeRequired将返回false。它应该没什么区别,因为当控件尚未完全创建时,你的调用将无论如何都会失败,但需要牢记这一点。
  2. 它不会使其线程安全。它只是将请求添加到控件的队列中,以便在创建控件的同一线程上执行下一个可用时间。这与Windows体系结构有关,而不是与一般的线程安全性有关。然而,最终结果是代码在单个线程上运行 - 当然,这仍然意味着您需要手动处理共享状态同步(如果有)。
  3. 嗯,这很复杂。但最后,它归结为比较创建控件的线程的线程ID和当前线程ID。在内部,它调用本机方法GetWindowThreadProcessId - 操作系统跟踪控件(更重要的是,它们的消息循环)。
  4. 在GUI线程返回其消息循环之前,
  5. Invoke无法返回。 Invoke本身只将命令发布到队列并等待处理。但该命令在GUI线程上运行,而不是Invoke - 调用者。因此,您的示例中的SomeMethod调用将被序列化,Invoke调用本身将等到第二个调用完成。
  6. 这应该已经回答了。关键点是“只在GUI线程上运行GUI代码”。这就是您始终获得可靠且响应灵敏的GUI的方式。
  7. 您可以在任何有循环或等待某个队列的地方使用它。虽然我实际上已经使用了几次(主要是在遗留代码中),但它可能并不是那么有用。
  8. 然而,所有这些只是对工作的简单解释。事实是,你不应该真的需要InvokeRequired ......好吧,永远。这是一个不同年龄的神器。这实际上主要是关于少量订单的杂乱线程,这不是一个好的做法。我看到的用途是延迟编码或遗留代码的修补程序 - 在新代码中使用它是愚蠢的。使用InvokeRequired的论点通常就像“它允许我们安全地处理这个业务逻辑,无论它是否在GUI线程中运行”。希望你可以看到这个逻辑的问题:)

    此外,它不是免费线程安全。它确实引入了延迟(特别是当GUI线程也在做一些非GUI的工作时 - 很可能在首先使用InvokeRequired的代码中)。它不会保护您免受其他线程对共享状态的访问。它可以引入死锁。甚至不要让我开始使用Application.DoEvents的代码做任何事情。

    当然,一旦考虑await,它就没那么有用了 - 编写异步代码非常容易,并且它允许您确保GUI代码始终在GUI上下文中运行,其余的可以在任何你想要的地方运行(如果它根本使用一个线程)。