12

まず、この質問にはなどの GUI 固有のタグが付けられていないことに注意してください。すぐにわかるように、これは意図的なものです。

第二に、この質問がやや長い場合は申し訳ありません。あちこちに散らばっているさまざまな情報を集めて、価値のある情報を提供するようにしています。ただし、私の質問は「知りたいこと」のすぐ下にあります。

私は最終的に、特定のスレッドでデリゲートを呼び出すために .NET によって提供されるさまざまな方法を理解することを使命としています。


私が知りたいこと:

  • 特定のスレッドでデリゲートを呼び出すための最も一般的な方法 (Winforms または WPF 固有ではない) を探しています。

  • または、別の言い方をすると、これを行うためのさまざまな方法 (WPFDispatcherを使用するなど) が相互に利用されているかどうか、およびその方法に興味があります。つまり、クロススレッド デリゲート呼び出しに共通のメカニズムが 1 つあり、それが他のすべてのメカニズムで使用されている場合です。


私がすでに知っていること:

  • このトピックに関連する多くのクラスがあります。その中で:

    • SynchronizationContext (中System.Threading)
      私が推測しなければならないとしたら、それは最も基本的なものでしょう。正確に何をするのか、どのように使用されるのかはわかりませんが。

    • AsyncOperation& (in ) これらは のラッパーのようです。それらを使用する方法の手がかりはありません。AsyncOperationManager System.ComponentModel
      SynchronizationContext

    • WindowsFormsSynchronizationContext (中System.Windows.Forms)
      のサブクラスSynchronizationContext

    • ISynchronizeInvoke (中System.ComponentModel)
      Windows フォームで使用されます。(Controlクラスはこれを実装しています。推測する必要がある場合、この実装は を利用していると思いますWindowsFormsSynchronizationContext。)

    • Dispatcher& (in ) 後者は の別のサブクラスのようで、前者はそれに委譲されています。DispatcherSynchronizationContext System.Windows.Threading
      SynchronizationContext

  • 一部のスレッドには、メッセージ キューと共に独自のメッセージ ループがあります。

    (MSDN のページAbout Messages and Message Queuesには、メッセージ ループがシステム レベルでどのように機能するか、つまり Windows API としてのメッセージ キューに関する入門的な背景情報があります。)

    メッセージ キューを使用してスレッドのクロススレッド呼び出しを実装する方法がわかります。PostThreadMessageWindows API を使用すると、デリゲートを呼び出す命令を含む特定のスレッドのメッセージ キューにメッセージを入れることができます。そのスレッドで実行されるメッセージ ループは最終的にそのメッセージに到達し、デリゲートが呼び出されます。

    MSDN で読んだことから、スレッドは自動的に独自のメッセージ キューを持ちません。メッセージキューは、スレッドがウィンドウを作成したときなどに利用可能になります。メッセージ キューがなければ、スレッドにメッセージ ループがあっても意味がありません。

    では、ターゲット スレッドにメッセージ ループがない場合、クロススレッド デリゲート呼び出しはまったく可能でしょうか? たとえば、.NET コンソール アプリケーションではどうでしょうか。(この質問への回答から判断すると、コンソールアプリでは確かに不可能だと思います。)

4

2 に答える 2

9

このような長い回答を投稿して申し訳ありません。しかし、正確に何が起こっているのかを説明する価値があると思いました。

あはは!私はそれを理解したと思います。特定のスレッドでデリゲートを呼び出す最も一般的な方法は、実際にはSynchronizationContextクラスのようです。

まず、.NET フレームワークは、任意のスレッドにデリゲートを単純に "送信" して、そこですぐに実行されるようにするデフォルトの手段を提供しません。明らかに、これは機能しません。なぜなら、そのスレッドがその時点で実行している作業を「中断」することを意味するからです。したがって、ターゲット スレッド自体が、いつ、どのようにデリゲートを "受け取る" かを決定します。つまり、この機能はプログラマが提供する必要があります。

したがって、ターゲット スレッドには、何らかの方法でデリゲートを「受け取る」必要があります。これは、さまざまな方法で行うことができます。簡単なメカニズムの 1 つは、スレッドがキューを参照するループ (「メッセージ ループ」と呼びましょう) に常に戻るようにすることです。キューにあるものは何でもうまくいきます。UI関連のものに関しては、Windowsはネイティブにこのように動作します。

以下では、メッセージ キューとその SynchronizationContextfor 、およびメッセージ ループを含むスレッドを実装する方法を示します。最後に、そのスレッドでデリゲートを呼び出す方法を示します。


例:

ステップ 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.
}

Post基本的に、これはユーザー提供のキューに渡されたすべてのデリゲートを追加する以上のことはしません。(Postは非同期呼び出しのメソッドです。Send同期呼び出しのメソッドです。後者は今のところ省略しています。)

ステップ 2.デリゲートが到着するのを待つスレッドZのコードをd書きましょう。

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

ステップ 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 フォーム アプリケーション、WPF アプリケーション、独自に考案したマルチスレッド コンソール アプリケーションのいずれを使用していても機能することです。Winforms と WPFSynchronizationContextの両方が、メイン/UI スレッドに適した を提供してインストールします。

特定のスレッドでデリゲートを呼び出す一般的な手順は次のとおりです。

  • そのスレッドに(同期的に) または(非同期的に) デリゲートできるように、ターゲット スレッド ( Zの)をキャプチャする必要があります。これを行う方法は、ターゲットスレッド Z にいる間に返された同期コンテキストを保存することですZ。(この同期コンテキストは、スレッドZで/によって事前に登録されている必要があります。) 次に、スレッドAからアクセスできる場所にその参照を格納します。SynchronizationContextSendPostSynchronizationContext.Current

  • スレッドAでは、キャプチャされた同期コンテキストを使用して、デリゲートをスレッドZに送信または投稿できます。zSyncContext.Post(_ => { ... }, null);

于 2011-01-30T15:28:03.617 に答える
4

メッセージループを持たないスレッドでデリゲートの呼び出しをサポートしたい場合は、基本的に独自に実装する必要があります。

メッセージ ループには特に魔法のようなものはありません。これは、通常のプロデューサー/コンシューマー パターンにおけるコンシューマーのようなものです。実行すること (通常は反応するイベント) のキューを保持し、それに応じて動作するキューを通過します。何もすることがなくなったら、何かがキューに入れられるまで待機します。

別の言い方をすれば、メッセージ ループを持つスレッドは、シングル スレッド スレッド プールと考えることができます。

これは、コンソール アプリを含め、自分で簡単に実装できます。スレッドが作業キューをループしている場合は、他のことも実行できないことに注意してください。通常、コンソール アプリでの実行のメイン スレッドは、一連のタスクを実行して終了することを意図しています。

BlockingCollection.NET 4 を使用している場合、クラスを使用してプロデューサー/コンシューマー キューを実装するのは非常に簡単です。

于 2011-01-30T13:06:59.143 に答える