【问题标题】:How InvokeRequired and Invoke let us make app thread safeInvokeRequired 和 Invoke 如何让我们使应用线程安全
【发布时间】:2015-01-27 12:26:02
【问题描述】:

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 之后和Invoke 之前更改InvokeRequired 的状态?如果不是,那为什么?

  2. Invoking 如何使其线程安全?

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

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

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

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

【问题讨论】:

  • 1.你应该把你的问题分成多个问题。 2. 为什么你认为Invoke 使它成为线程安全的? Invoke 负责在 UI 线程上排队工作,以便可以访问线程仿射的 UI 元素。
  • 你应该使用事件在线程之间进行通信。
  • @user743414 您可能想要更具体一点。事件本身没有任何线程安全或跨线程。
  • @Luaan 当你必须使用 InvokeRequired 时,其他的东西是错误的(或者至少设计不好)。那是因为您正试图从另一个线程更改您的 UI 线程。 UI 线程应该仅显示数据。您的工作线程(无论他们做什么)应该直接操作数据对象而不是 UI。 UI 可以通过事件通知,例如显示修改后的数据。使数据对象线程安全应该很容易。
  • @user743414 InvokeRequired 是代码异味(正如我在回答中概述的那样)。但是您的解决方案实际上并没有帮助 - 您仍然需要处理数据的正确同步。如果您的工作线程调用事件,处理程序仍将在工作线程上运行。 Invoke 至少确保您可以假装您的控件是演员,确保命令在单个线程上序列化。我不提倡使用Invoke。我并不是说工作线程应该直接访问 UI,但它确实需要能够以线程安全的方式将数据发送给进行 UI 更新的任何人。

标签: c# multithreading


【解决方案1】:

是否可以改变 InvokeRequired 的状态

是的,这是很常见的情况。要么是因为您在表单的 Load 事件触发之前过早地启动了线程。或者因为用户在此代码运行时关闭了窗口。在这两种情况下,此代码都会失败并出现异常。当线程在窗口创建之前竞争时,InvokeRequired 失败,当 UI 线程在线程之前竞争时,调用的代码失败。发生异常的几率很低,在您测试代码时无法诊断出错误。

Invoking 如何使它成为线程安全的?

您无法使用此代码使其安全,这是一场基本竞赛。必须通过将窗口的关闭与线程执行联锁来确保它的安全。您必须确保线程在允许窗口关闭之前停止。 this answer的主题。

他怎么知道自己是或不是所有者。

这可以通过 winapi 调用发现,GetWindowsThreadProcessId()Handle 属性是它的基本预言。相当不错的测试,但有一个明显的缺陷,即当句柄不再有效时它无法工作。通常使用 oracle 是不明智的,您应该始终知道代码何时在工作线程上运行。这样的代码与在 UI 线程上运行的代码有着根本的不同。这是慢代码。

我们想从 Thread2 调用它

这通常是不可能的。将一个线程的调用编组到 特定 其他线程需要其他线程进行合作。它必须解决producer-consumer problem。看一下链接,该问题的根本解决方案是调度程序循环。你可能认识它,这就是程序的 UI 线程的运行方式。 必须解决这个问题,它从任意其他线程获取通知,并且 UI 永远不是线程安全的。但是工作线程一般不会尝试自己解决这个问题,除非你明确地写出来,你需要一个线程安全的Queue和一个清空它的循环。

如果 SomeMethod() 需要很长时间会怎样

我不确定我是否遵循,使用线程的目的是让需要很长时间的代码不会做任何损害用户界面响应的事情。

我认为这是某种通用模式

有,看起来不像。这类代码往往是在您有一个 oh-shoot 时刻并发现您的 UI 冻结时编写的。在从未设计为支持线程的代码之上绑定线程永远是一个坏主意。你会忽略太多令人讨厌的小细节。 最小化工作线程与 UI 线程交互的次数非常重要,您的代码正在做相反的事情。与 BackgroundWorker 类一起陷入了成功的坑,它的 RunWorkerCompleted 事件提供了一个很好的同步方式来更新 UI 与后台操作的结果。如果您喜欢 Tasks,那么 TaskScheduler.FromCurrentSynchronizationContext() 方法可以帮助您本地化交互。

【讨论】:

  • 在很多情况下,人们可能不希望关闭窗口影响线程,而只是导致它的更新被忽略(例如,如果应用程序允许打开窗口以监视状态)一些更大的过程)。可以使用锁和标志来确保不会尝试将事件发布到正在处理的窗口,但我认为在某些方面编写 TryInvokeTryBeginInvoke 返回一个如果窗口不能接受事件,而不是抛出异常。
【解决方案2】:
  1. 通常不会。但是,如果您在 InvokeRequired 检查和 Invoke 调用之间使用 await 而不捕获执行上下文,则可能会发生这种情况。当然,如果您已经在使用await,您可能不会使用InvokeInvokeRequired编辑:我刚刚注意到InvokeRequired 将在尚未创建控制句柄时返回false。这应该没什么区别,因为当控件尚未完全创建时,您的调用无论如何都会失败,但请记住这一点。
  2. 它不会使其成为线程安全的。它只是将请求添加到控件的队列中,以便在下一个可用时间在创建控件的同一线程上执行。这与 Windows 体系结构有关,而不是与一般线程安全有关。然而,最终结果是代码在单个线程上运行 - 当然,这仍然意味着您需要手动处理共享状态同步(如果有的话)。
  3. 嗯,这很复杂。但归根结底,它归结为比较创建控件的线程的线程 ID 和当前线程 ID。在内部,这会调用原生方法 GetWindowThreadProcessId - 操作系统会跟踪控件(更重要的是,它们的消息循环)。
  4. Invoke 在 GUI 线程返回其消息循环之前无法返回。 Invoke 本身只将命令发布到队列并等待它被处理。但该命令在 GUI 线程上运行,而不是在 Invoke-caller 上运行。因此,您示例中的 SomeMethod 调用将被序列化,而 Invoke 调用本身将等到第二次调用完成。
  5. 这应该已经回答了。关键点是“只在 GUI 线程上运行 GUI 代码”。这就是您始终获得可靠且响应迅速的 GUI 的方式。
  6. 您可以在任何有循环或等待某个队列的地方使用它。它可能不是那么有用,尽管我实际上已经使用过几次(主要是在遗留代码中)。

但是,所有这些都只是对工作原理的简单解释。事实是,你不应该真的需要InvokeRequired...好吧,永远。这是不同时代的神器。这实际上主要是关于杂乱无章的线程,这并不是一个好的做法。我见过的用途要么是惰性编码,要么是遗留代码的修补程序——在新代码中使用它是愚蠢的。使用InvokeRequired 的论据通常类似于“它允许我们安全地处理这个业务逻辑,无论它是否在GUI 线程中运行”。希望你能看到这个逻辑的问题:)

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

当然,一旦你考虑到await,它的用处就更小了——编写异步代码要容易得多,它允许你确保 GUI 代码总是在 GUI 上下文中运行,其余代码可以在任何地方运行你想要(如果它使用线程的话)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-09-11
    • 2011-04-03
    • 1970-01-01
    • 2023-04-01
    • 2011-07-04
    • 2013-10-21
    • 1970-01-01
    • 2011-06-06
    相关资源
    最近更新 更多