.NET:如何调用一个特定线程的委托? (ISynchronizeInvoke,调度,的AsyncOperation,SynchronizationContext的,等等)线程、NET、ISynchr

2023-09-04 01:41:09 作者:最痛不过心死

注意首先,这个问题是没有标记的的WinForms 或 WPF 或其他任何GUI的特定。这是故意的,因为你很快就会看到。的

     

二,对不起,如果这个问题是有点长。我试着拉了信息漂浮在这里和那里在一起的各个位,从而也提供了有价值的信息。我的问题,但是,是正确的我想知道。的

我的任务终于明白通过.NET提供来调用一个特定的线程委托的各种方式。

我想知道:

我在寻找可能的最普遍的方式(即不的WinForms或WPF专用)来调用特定的线程代表。

或者,措辞不同:我有兴趣的话,又如何,不同的方式来做到这一点(比如通过WPF的调度)使用各其他;也就是说,如果有一个共同的机制为所使用的所有其它的跨线程委托调用

什么我已经知道了:

有与此相关的话题很多类;其中:

的SynchronizationContext 的(在的System.Threading )的 如果我猜的话,这将是最基本的;虽然我不明白究竟这样做,也没有如何使用它。

的AsyncOperation 和放大器; AsyncOperationManager 的(在 System.ComponentModel )的 这似乎是围绕的SynchronizationContext 包装。不知道如何使用它们。

WindowsFormsSynchronizationContext 的(在 System.Windows.Forms的的SynchronizationContext 的子类。

我想用c语言编一个程序当输入A时,调用一个音频文件,怎么写

ISynchronizeInvoke 的(在 System.ComponentModel )的 使用Windows窗体。 (该控制类实现了这一点。如果我不得不猜测,我会说这实现使用 WindowsFormsSynchronizationContext )

调度 和放大器; DispatcherSynchronizationContext 的(在 System.Windows.Threading程序) 好像后者是的SynchronizationContext 的另一个子类,前者代表了。

某些线程有自己的消息循环,还有一个消息队列。

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

我可以看到如何能实现跨线程调用的一个消息队列的线程。使用Windows API,你可以把一个消息到特定线程的消息队列通过的 PostThreadMessage 包含一个指令调用一些委托。该消息循环—它运行在该线程—将最终到达该消息,并且代表将被调用。

从我读过MSDN上,线程不会自动拥有自己的消息队列。消息队列将变得可用例如当一个线程创建的一个窗口。如果没有消息队列,它没有任何意义的线程有消息循环。

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

解决方案   

对不起张贴这么长的答案。但我认为它值得解释究竟是怎么回事。的

A-HA!我想我已经想通弄明白了。调用代理上的特定线程的最通用的方法确实似乎是的SynchronizationContext 类。

首先,.NET框架做的没有的提供一个默认的意思简单地送委托来的任意的线程,这样它会得到立即执行那里。显然,这不能工作,因为这将意味着中断力所能及的工作线程会做的时间。因此,目标线程本身决定如何以及何时将接收的代表;也就是说,该功能必须由程序员提供。

因此​​,一个目标线程需要的接收代表某种方式。这可以在许多不同的方式来完成。一个简单的机制是线程总是回到一些环路(我们称之为消息循环),在那里将看一个队列。它会工作过无论是在队列中。窗户本身是这样工作的,当涉及到用户界面相关的东西。

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

例:

第1步让我们先来创建的SynchronizationContext 类那将是与目标线程的消息中使用队列:

 类QueueSyncContext:的SynchronizationContext
{
    私人只读ConcurrentQueue< SendOrPostCallback>队列;

    公共QueueSyncContext(ConcurrentQueue< SendOrPostCallback>队列)
    {
        this.queue =队列;
    }

    公众覆盖无效后(SendOrPostCallback研发,对象的状态)
    {
        queue.Enqueue(四);
    }

    //实现发送()中省略在本实施例为简单起见。
}
 

基本上,这并不比补充说,通过发表传递给用户提供的队列中的所有与会代表做多。 (发布是方法的异步调用。发送将是同步调用,我忽略了后者现在。)

第2步。现在让我们写在 $ C $下一个线程的以Z 的是等待代表 D 抵达

 的SynchronizationContext syncContextForThreadZ = NULL;

无效MainMethodOfThreadZ()
{
    //这将被用作线程的消息队列:
    VAR队列=新ConcurrentQueue< PostOrCallDelegate>();

    //设置同步环境为我们的消息处理:
    syncContextForThreadZ =新QueueSyncContext(队列);
    SynchronizationContext.SetSynchronizationContext(syncContextForThreadZ);

    //这里的消息循环(效率不高,这是为演示目的只有:)
    而(真)
    {
        PostOrCallDelegate D = NULL;
        如果(queue.TryDequeue(输出D))
        {
            d.Invoke(空);
        }
    }
}
 

第3步发的以Z 的需要在某个地方启动:

 新主题(新的ThreadStart(MainMethodOfThreadZ))启动();
 

第四步最后,背部的上的一些其他线程的 A 的,我们要派代表到线程的以Z 的:

 无效SomeMethodOnThreadA()
{
    //线程Z应该在运行起来后,才可以派出代表到它:
    而(syncContextForThreadZ == NULL);

    syncContextForThreadZ.Post(_ =>
        {
            Console.WriteLine(这将运行在线程ž!);
        },
        空值);
}
 

有关这样做的好处是,的SynchronizationContext 的作品,不管你是在Windows窗体应用程序,在WPF应用程序,或在多线程控制台应用自己的设计。无论WinForms和WPF提供并安装合适的SynchronizationContext S表示他们的主要/ UI线程。

用于调用一个代表在特定线程的一般过程如下:

您必须捕获目标线程的(以Z 的的)的SynchronizationContext ,这样就可以发送(同​​步)或者发布(异步)委托给该线程。该路该怎么做,这是存储由 SynchronizationContext.Current 返回当你在目标线程的同步上下文的以Z 的。 (此同步的情况下必须有previously已经注册/由线程的以Z 的。)然后存储参考的地方在那里它是由线程访问的 A 的

在上线的 A 的,你可以用捕获的同步上下文发送或张贴任何代表线程的以Z 的 zSyncContext。帖子(_ => {...},空);

Note first of all that this question is not tagged winforms or wpf or anything else GUI-specific. This is intentional, as you will see shortly.

Second, sorry if this question is somewhat long. I try to pull together various bits of information floating around here and there so as to also provide valuable information. My question, however, is right under "What I would like to know".

I'm on a mission to finally understand the various ways offered by .NET to invoke a delegate on a specific thread.

What I would like to know:

I am looking for the most general way possible (that is not Winforms or WPF-specific) to invoke delegates on specific threads.

Or, phrased differently: I would be interested if, and how, the various ways to do this (such as via WPF's Dispatcher) make use of each other; that is, if there is one common mechanism for cross-thread delegate invocation that's used by all the others.

What I know already:

There's many classes related to this topic; among them:

SynchronizationContext (in System.Threading) If I had to guess, that would be the most basic one; although I don't understand what exactly it does, nor how it is used.

AsyncOperation & AsyncOperationManager (in System.ComponentModel) These seem to be wrappers around SynchronizationContext. No clue how to use them.

WindowsFormsSynchronizationContext (in System.Windows.Forms) A subclass of SynchronizationContext.

ISynchronizeInvoke (in System.ComponentModel) Used by Windows Forms. (The Control class implements this. If I'd have to guess, I'd say this implementation makes use of WindowsFormsSynchronizationContext.)

Dispatcher &DispatcherSynchronizationContext (in System.Windows.Threading) Seems like the latter is another subclass of SynchronizationContext, and the former delegates to it.

Some threads have their own message loop, along with a message queue.

(The MSDN page About Messages and Message Queues has some introductory background information on how message loops work at the system level, i.e. message queues as the Windows API.)

I can see how one would implement cross-thread invocation for threads with a message queue. Using the Windows API, you would put a message into a specific thread's message queue via PostThreadMessage that contains an instruction to call some delegate. The message loop — which runs on that thread — will eventually get to that message, and the delegate will be called.

From what I've read on MSDN, a thread doesn't automatically have its own message queue. A message queue will become available e.g. when a thread has created a window. Without a message queue, it doesn't make sense for a thread to have a message loop.

So, is cross-thread delegate invocation possible at all when the target thread has no message loop? Let's say, in a .NET console application? (Judging from the answers to this question, I suppose it's indeed impossible with console apps.)

解决方案

Sorry for posting such a long answer. But I thought it worth explaining what exactly is going on.

A-ha! I think I've got it figured out. The most generic way of invoking a delegate on a specific thread indeed seems to be the SynchronizationContext class.

First, the .NET framework does not provide a default means to simply "send" a delegate to any thread such that it'll get executed there immediately. Obviously, this cannot work, because it would mean "interrupting" whatever work that thread would be doing at the time. Therefore, the target thread itself decides how, and when, it will "receive" delegates; that is, this functionality has to be provided by the programmer.

So a target thread needs some way of "receiving" delegates. This can be done in many different ways. One easy mechanism is for the thread to always return to some loop (let's call it the "message loop") where it will look at a queue. It'll work off whatever is in the queue. Windows natively works like this when it comes to UI-related stuff.

In the following, I'll demonstrate how to implement a message queue and a SynchronizationContext for it, as well as a thread with a message loop. Finally, I'll demonstrate how to invoke a delegate on that thread.

Example:

Step 1. Let's first create a SynchronizationContext class that'll be used together with the target thread's message queue:

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.
}

Basically, this doesn't do more than adding all delegates that are passed in via Post to a user-provided queue. (Post is the method for asynchronous invocations. Send would be for synchronous invocations. I'm omitting the latter for now.)

Step 2. Let's now write the code for a thread Z that waits for delegates d to arrive:

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);
        }
    }
}

Step 3. Thread Z needs to be started somewhere:

new Thread(new ThreadStart(MainMethodOfThreadZ)).Start();

Step 4. Finally, back on some other thread A, we want to send a delegate to thread 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);
}

The nice thing about this is that SynchronizationContext works, no matter whether you're in a Windows Forms application, in a WPF application, or in a multi-threaded console application of your own devising. Both Winforms and WPF provide and install suitable SynchronizationContexts for their main/UI thread.

The general procedure for invoking a delegate on a specific thread is the following:

You must capture the target thread's (Z's) SynchronizationContext, so that you can Send (synchronously) or Post (asynchronously) a delegate to that thread. The way how to do this is to store the synchronization context returned by SynchronizationContext.Current while you're on the target thread Z. (This synchronization context must have previously been registered on/by thread Z.) Then store that reference somewhere where it's accessible by thread A.

While on thread A, you can use the captured synchronization context to send or post any delegate to thread Z: zSyncContext.Post(_ => { ... }, null);