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

sta*_*ica 10 .net winapi multithreading system.componentmodel synchronizationcontext

首先请注意,此问题未标记为或其他任何GUI特定的.这是故意的,你很快就会看到.

第二,对不起,如果这个问题有点长.我尝试将各种各样的信息汇集在一起​​,以便提供有价值的信息.然而,我的问题正好在"我想知道的事情"之下.

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


我想知道的是:

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

  • 或者,换句话说:我会感兴趣的是,以及如何做各种各样的方式(例如通过WPF Dispatcher)相互利用; 也就是说,如果所有其他人都使用了一种用于跨线程委托调用的通用机制.


我所知道的:

  • 有很多与此主题相关的课程; 其中:

  • 有些线程有自己的消息循环,还有消息队列.

    (MSDN页面关于消息和消息队列有一些介绍消息循环如何在系统级工作的背景信息,即消息队列作为Windows API.)

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

    根据我在MSDN上阅读的内容,线程不会自动拥有自己的消息队列.消息队列将变为可用,例如当线程创建窗口时.没有消息队列,线程没有消息循环是没有意义的.

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

sta*_*ica 8

很抱歉发布这么长的答案.但我认为值得解释究竟发生了什么.

A-HA!我想我已经明白了.在特定线程上调用委托的最通用方式确实似乎是SynchronizationContext类.

首先,.NET框架没有提供一种默认方法来简单地将委托"发送"到任何线程,以便它立即在那里执行.显然,这不起作用,因为它意味着"打断"线程当时正在做的任何工作.因此,目标线程本身决定它将"接收"委托的方式和时间; 也就是说,该功能必须由程序员提供.

因此目标线程需要一些"接收"委托的方式.这可以通过许多不同的方式完成.一个简单的机制是线程总是返回某个循环(让我们称之为"消息循环"),它将查看队列.它可以解决队列中的任何问题.当涉及到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.
}
Run Code Online (Sandbox Code Playgroud)

基本上,这不仅仅是将通过的所有委托添加Post到用户提供的队列中.(Post是异步调用的方法.Send将用于同步调用.我现在省略后者.)

步骤2.现在让我们编写一个等待代表到达的线程Zd代码:

SynchronizationContext syncContextForThreadZ = null;

void MainMethodOfThreadZ()
{
    // this will be used as the thread's message queue:
    var queue = new ConcurrentQueue<PostOrCallDelegate>();

    // set up a synchronization context for our message processing:
    syncContextForThreadZ = new QueueSyncContext(queue);
    SynchronizationContext.SetSynchronizationContext(syncContextForThreadZ);

    // here's the message loop (not efficient, this is for demo purposes only:)
    while (true)
    {
        PostOrCallDelegate d = null;
        if (queue.TryDequeue(out d))
        {
            d.Invoke(null);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

第3步.线程Z需要在某处启动:

new Thread(new ThreadStart(MainMethodOfThreadZ)).Start();
Run Code Online (Sandbox Code Playgroud)

步骤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);
}
Run Code Online (Sandbox Code Playgroud)

关于这样做的好处是,SynchronizationContext工作,不管你在Windows是窗体应用程序,在WPF应用程序,或在您自己设计的多线程控制台应用程序.Winforms和WPF都SynchronizationContext为其主/ UI线程提供并安装合适的s.

在特定线程上调用委托的一般过程如下:

  • 您必须捕获目标线程(Z的)SynchronizationContext,以便您可以Send(同步)或Post(异步)委托该线程.如何执行此操作的方法是存储SynchronizationContext.Current在目标线程Z上返回的同步上下文.(此同步上下文必须先前已经/通过线程Z注册.)然后将该引用存储在线程A可访问的位置.

  • 在线程A上,您可以使用捕获的同步上下文将任何委托发送或发布到线程Z:zSyncContext.Post(_ => { ... }, null);