Winforms .NET:如何在特定线程上调用委托?(ISynchronizeInvoke、Dispatcher、AsyncOperation、SynchronizationContext等)

Winforms .NET:如何在特定线程上调用委托?(ISynchronizeInvoke、Dispatcher、AsyncOperation、SynchronizationContext等),winforms,wpf,.net,winapi,multithreading,system.componentmodel,synchronizationcontext,Winforms,Wpf,.net,Winapi,Multithreading,System.componentmodel,Synchronizationcontext,首先请注意,这个问题没有标记或任何其他特定于GUI的内容。这是有意的,你很快就会看到 第二,对不起,如果这个问题有点长。我试图将四处飘荡的各种信息汇集在一起,以便提供有价值的信息。然而,我的问题就在“我想知道什么”下面 我的任务是最终理解.NET提供的在特定线程上调用委托的各种方法 我想知道的是: 我正在寻找在特定线程上调用委托的最通用的方法(不是Winforms或WPF特定的) 或者,用不同的措辞:我会感兴趣的是,如果以及如何做到这一点的各种方式(如通过WPF的Dispatcher)相互利

首先请注意,这个问题没有标记或任何其他特定于GUI的内容。这是有意的,你很快就会看到

第二,对不起,如果这个问题有点长。我试图将四处飘荡的各种信息汇集在一起,以便提供有价值的信息。然而,我的问题就在“我想知道什么”下面

我的任务是最终理解.NET提供的在特定线程上调用委托的各种方法


我想知道的是:
  • 我正在寻找在特定线程上调用委托的最通用的方法(不是Winforms或WPF特定的)

  • 或者,用不同的措辞:我会感兴趣的是,如果以及如何做到这一点的各种方式(如通过WPF的
    Dispatcher
    )相互利用;也就是说,如果有一种用于跨线程委托调用的公共机制被所有其他机制使用的话


我已经知道:
  • 有许多课程与这个主题相关;其中:

    • (在
      系统线程中

      如果让我猜的话,那将是最基本的一个;虽然我不知道它到底是做什么的,也不知道它是如何使用的

    • &(在
      系统组件模型中

      这些似乎是
      SynchronizationContext
      的包装。不知道如何使用它们

    • (在
      System.Windows.Forms中

      SynchronizationContext
      的子类

    • (在
      System.ComponentModel
      中)
      由Windows窗体使用。(控件
      类实现了这一点。如果我不得不猜测的话,我会说这个实现使用了
      WindowsFormsSynchronizationContext

    • &(在
      System.Windows.Threading
      )中
      后者似乎是
      SynchronizationContext
      的另一个子类,前者委托给它

  • 有些线程有自己的消息循环和消息队列

    (MSDN页面有一些介绍性的背景信息,介绍了消息循环在系统级别的工作方式,即作为Windows API的消息队列。)

    我可以看到如何使用消息队列为线程实现跨线程调用。使用Windows API,您可以通过将消息放入特定线程的消息队列,该队列包含调用某个委托的指令。在该线程上运行的消息循环最终将到达该消息,并调用委托

    ,线程不会自动拥有自己的消息队列。消息队列将变得可用,例如,当线程创建了一个窗口时。没有消息队列,线程没有消息循环是没有意义的

    那么,当目标线程没有消息循环时,跨线程委托调用是否可能呢?比方说,在.NET控制台应用程序中?(从问题的答案来看,我认为使用控制台应用程序确实是不可能的。)


如果您想支持在没有消息循环的线程上调用委托,基本上您必须实现自己的

消息循环没有什么特别神奇的地方:它就像普通生产者/消费者模式中的消费者一样。它保留一个要做的事情的队列(通常是要响应的事件),并相应地通过队列执行操作。当无事可做时,它会等到队列中有东西被放入

换句话说:您可以将具有消息循环的线程视为单线程线程池

您可以自己轻松地实现这一点,包括在控制台应用程序中。只要记住,如果线程在工作队列中循环,它就不能做其他事情——而控制台应用程序中执行的主线程通常是执行一系列任务,然后完成

如果您使用的是.NET4,那么使用类实现生产者/消费者队列非常容易

很抱歉发布了这么长的答复。但我认为有必要解释到底发生了什么

哈哈!我想我已经弄明白了。在特定线程上调用委托的最通用方法似乎确实是
SynchronizationContext

首先,.NET framework不提供默认方法,即简单地将委托“发送”到任何线程,以便立即在那里执行委托。显然,这是行不通的,因为这意味着“中断”线程当时正在做的任何工作。因此,目标线程本身决定如何以及何时“接收”委托;也就是说,此功能必须由程序员提供

因此,目标线程需要某种方式“接收”委托。这可以通过许多不同的方式实现。一个简单的机制是线程总是返回到某个循环(让我们称之为“消息循环”),在那里它将查看队列。它会解决队列中的任何问题。当涉及到与UI相关的东西时,Windows本机就是这样工作的

在下面,我将演示如何为其实现消息队列和
SynchronizationContext
,以及带有消息循环的线程。最后,我将演示如何在该线程上调用委托


例子: 步骤1。让我们首先创建一个
SynchronizationContext
类,该类将与目标线程的消息队列一起使用:

class QueueSyncContext : SynchronizationContext
{
    private readonly ConcurrentQueue<SendOrPostCallback> queue;

    public QueueSyncContext(ConcurrentQueue<SendOrPostCallback> queue)
    {
        this.queue = queue;
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        queue.Enqueue(d);
    }

    // implementation for Send() omitted in this example for simplicity's sake.
}
步骤3。线程Z需要从某个地方开始:

new Thread(new ThreadStart(MainMethodOfThreadZ)).Start();
步骤4。最后,回到其他线程A,我们想向线程Z发送一个委托:

void SomeMethodOnThreadA()
{
    // thread Z must be up and running before we can send delegates to it:
    while (syncContextForThreadZ == null) ;

    syncContextForThreadZ.Post(_ =>
        {
            Console.WriteLine("This will run on thread Z!");
        },
        null);
}

这一点的好处是,
SynchronizationContext
可以工作,无论您是否在Windows系统中
void SomeMethodOnThreadA()
{
    // thread Z must be up and running before we can send delegates to it:
    while (syncContextForThreadZ == null) ;

    syncContextForThreadZ.Post(_ =>
        {
            Console.WriteLine("This will run on thread Z!");
        },
        null);
}