【问题标题】:C# Basic Multi-Threading Question: Call Method on Thread A from Thread B (Thread B started from Thread A)C#基本多线程问题:从线程B调用线程上的方法(线程B从线程A开始)
【发布时间】:2011-01-29 01:58:44
【问题描述】:

完成此任务的最佳方法是:主线程(线程 A)创建另外两个线程(线程 B 和线程 C)。线程 B 和 C 执行繁重的磁盘 I/O,最终需要将他们创建的资源传递给线程 A,然后调用外部 DLL 文件中的方法,该方法需要正确调用创建它的线程,因此只有线程 A 可以调用它.

我唯一一次使用线程是在 Windows 窗体应用程序中,而调用方法正是我所需要的。该程序不使用 Windows 窗体,因此没有可使用的 Control.Invoke 方法。

我在测试中注意到,如果在线程 A 中创建了一个变量,我可以毫无问题地从线程 B/C 访问和修改它,这对我来说似乎是非常错误的。使用 Winforms,我确信它会在尝试访问在其他线程上创建的内容时引发错误。我知道从多个线程更改内容是不安全的,但我真的希望 .NET 完全禁止它以确保安全编码。 .NET 能做到这一点,而我只是错过了这艘船,还是只在 WinForm 应用程序中做到这一点?

既然它看起来确实允许这样做,我是否会像操作系统那样做一些事情,创建一个标志并从线程 A 监视它以查看它是否发生变化。如果是,则调用该方法。事件处理程序本质上不是这样做的,所以可以在主线程上以某种方式调用事件吗?

【问题讨论】:

    标签: c# multithreading controls invoke


    【解决方案1】:

    通常,这是不必要的。您可以从任何线程调用任何对象的方法,这是一件好事...... UI 组件和一些遗留 COM 组件往往是唯一必须从特定线程访问的项目。

    通常,您将尝试使用同步(即:lock(...) 等)来保护对数据本身的访问,并使其安全工作,而不是尝试在不同的线程上调用方法来自多个线程。

    为您提供更细粒度的保护,有利于性能。使用 Control.Invoke 实际上非常昂贵,因为它使用 Windows 消息传递来标记它需要运行的线程。

    话虽如此,这实际上是可以做到的,尽管相当困难。 “诀窍”是您可以使用自己的线程创建对象,并让该对象提供SynchronizationContext。然后,您可以使用 SynchronizationContext.Post 或 Send 在该上下文中运行方法。

    这很难做到,而且没有什么理由这样做,所以正如我所说,我不建议在大多数情况下使用它。

    【讨论】:

    • 谢谢,我会调查 SynchronizationContext。这不是我想做的事情,这是我被外部(不是我自己的)DLL 强迫做的事情。 :(
    • 制作自己的 SynchronizationContext 非常困难。我建议在类中使用 Queue,并改为在生产者/消费者类型的场景中工作。不要试图“在另一个线程上运行一个方法”,只需将它添加到队列中,然后让另一个线程将它从队列中拉出并运行它。
    【解决方案2】:

    “我确信它会在尝试访问在其他线程上创建的东西时引发错误。”此语句仅对控件的属性有效。通用变量可以从任何线程访问。是的,如果做得不正确,它可能会导致问题(竞争条件),但有时这是必要的。您应该将此变量标记为易失性,尽量减少对它们的访问,使用锁或互锁类。这是我的建议:)

    【讨论】:

      【解决方案3】:

      首先,“Winforms”不会仅仅因为试图从另一个线程访问数据而引发错误。那是不可能的;无法知道哪个线程最初声明了某个变量或创建了一些数据。在 Winforms 中导致异常的作用 是尝试从 UI 线程以外的任何线程调用 Control 后代的方法 - 这是一个非常特殊的特殊情况。除此之外,Winforms 只是一个库,运行在与任何其他 .NET 应用程序完全相同的运行时之上,并且对线程的管理方式没有特殊控制。

      对于 .NET Framework 或任何其他库来说,绝对没有办法“确保”安全的多线程代码。某些概念有助于减少竞争条件和其他并发错误,例如 .NET 4 的 Concurrent Collections 库、不可变数据类型等,但使用它们取决于您。当您编写多线程代码时,您将承担与之相关的所有风险和责任。程序员要确保你的代码是线程安全的。

      您在这里提到的具体案例似乎相当简单。我喜欢使用线程池,所以我将向您展示使用事件的解决方案;您也可以使用实际的Thread 对象并使用Thread.Join。假设我们在线程 A 中:

      void Xyz()
      {
          SomeData dataFromB = null;
          ManualResetEvent finishedB = new ManualResetEvent(false);
          ThreadPool.QueueUserWorkItem(s =>
          {
              dataFromB = GetDataFromB();
              finishedB.Set();
          });
      
          SomeOtherData dataFromC = null;
          ManualResetEvent finishedC = new ManualResetEvent(false);
          ThreadPool.QueueUserWorkItem(s =>
          {
              dataFromC = GetDataFromC();
              finishedC.Set();
          });
      
          finishedB.WaitOne();
          finishedC.WaitOne();
          finishedB.Dispose();
          finishedC.Dispose();
      
          CallExternalDLL(dataFromB, dataFromC);
      }
      

      这将阻塞直到整个操作完成。如果您直接从 UI 线程运行此方法,则应从另一个工作线程调用整个方法。

      【讨论】:

        【解决方案4】:

        从我的观点来看,对您来说最简单的方法是使用同步队列,B 和 C 会将项目排队到其中。
        然后线程 A 将从队列中出列,并可能等待新的东西入队。
        将 Doug 在此 MSDN article 中的评论视为此类队列的一个简单示例。 您还可以将委托加入队列以确保代码在正确的线程中运行。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多