。ネット:特定のスレッドでデリゲートを呼び出すにはどうすればよいですか?(ISynchronizeInvoke、Dispatcher、AsyncOperation、SynchronizationContext など)
-
27-10-2019 - |
質問
まず最初に、この質問にはタグが付けられていないことに注意してください winforms または うわー またはその他の GUI 固有のもの。すぐにわかるように、これは意図的なものです。
次に、この質問が少し長くなってしまい申し訳ありません。あちこちに転がっている様々な情報をまとめて、有益な情報を提供するように努めています。しかし、私の質問は「知りたいこと」のすぐ下にあります。
私には、特定のスレッドでデリゲートを呼び出すために .NET が提供するさまざまな方法を最終的に理解するという使命があります。
知りたいこと:
特定のスレッドでデリゲートを呼び出すために可能な最も一般的な方法 (Winforms または WPF 固有ではない) を探しています。
または、別の言い方をすると、次のようになります。これを行うためのさまざまな方法 (WPF 経由など) があるかどうか、またその方法に興味があります。
Dispatcher
)お互いを利用する。つまり、他のすべてのメカニズムで使用されるクロススレッド デリゲート呼び出し用の共通メカニズムが 1 つある場合です。
私がすでに知っていること:
このトピックに関連するクラスが多数あります。その中で:
SynchronizationContext
(でSystem.Threading
)
推測しなければならないとしたら、それが最も基本的なものになるでしょう。とはいえ、それが正確に何をするのか、どのように使用されるのかはわかりません。AsyncOperation
&AsyncOperationManager
(でSystem.ComponentModel
)
これらはラッパーのようですSynchronizationContext
. 。使い方が全く分かりません。WindowsFormsSynchronizationContext
(でSystem.Windows.Forms
)
のサブクラスSynchronizationContext
.ISynchronizeInvoke
(でSystem.ComponentModel
)
Windows フォームで使用されます。(Control
クラスはこれを実装します。推測する必要があるとすれば、この実装では次のものを利用していると思います。WindowsFormsSynchronizationContext
.)Dispatcher
&DispatcherSynchronizationContext
(でSystem.Windows.Threading
)
後者は別のサブクラスのようですSynchronizationContext
, 、前者はそれに委任されます。
一部のスレッドには、メッセージ キューとともに独自のメッセージ ループがあります。
(MSDN ページ メッセージとメッセージキューについて には、メッセージ ループがシステム レベルでどのように機能するかについての基礎的な背景情報が含まれています。メッセージ キューを Windows API として使用します)。
メッセージキューを使用してスレッドのクロススレッド呼び出しを実装する方法がわかります。Windows API を使用すると、次のようにして特定のスレッドのメッセージ キューにメッセージを入れます。
PostThreadMessage
これには、デリゲートを呼び出す命令が含まれています。そのスレッド上で実行されるメッセージ ループは最終的にそのメッセージに到達し、デリゲートが呼び出されます。MSDNで読んだことによると, 、スレッドは自動的に独自のメッセージ キューを持ちません。メッセージキューが利用可能になります。スレッドがウィンドウを作成したとき。メッセージ キューがなければ、スレッドにメッセージ ループがあっても意味がありません。
では、ターゲット スレッドにメッセージ ループがない場合、クロススレッド デリゲートの呼び出しは可能でしょうか?たとえば、.NET コンソール アプリケーションでしょうか?(回答から判断すると、 この質問, 、コンソール アプリでは確かに不可能だと思います。)
解決
このような長い回答を投稿して申し訳ありません。しかし、正確に何が起こっているのかを説明する価値があると思いました。
ああ!理解できたと思います。特定のスレッドでデリゲートを呼び出す最も一般的な方法は、確かに次のとおりです。 SynchronizationContext
クラス。
まず、.NET Framework は次のことを行います。 ない 単にデリゲートを「送信」するデフォルトの手段を提供します どれでも そこですぐに実行されるようにスレッドを作成します。明らかに、これは機能しません。これは、スレッドがその時点で行っている作業を「中断」することになるからです。したがって、ターゲット スレッド自体が、デリゲートをいつどのように「受信」するかを決定します。つまり、この機能はプログラマによって提供される必要があります。
したがって、ターゲットスレッドにはデリゲートを「受信」する何らかの方法が必要です。これはさまざまな方法で行うことができます。簡単なメカニズムの 1 つは、スレッドが常にキューを調べるループ (「メッセージ ループ」と呼びます) に戻ることです。キューにあるものは何でも機能します。Windows は、UI 関連に関してはネイティブにこのように動作します。
以下では、メッセージ キューと 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.
}
基本的に、これは、経由で渡されるすべてのデリゲートを追加する以上のことは行いません。 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。 最後に、戻って 他のスレッドで あ, 、スレッドにデリゲートを送信したい 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 アプリケーション、または独自に考案したマルチスレッド コンソール アプリケーションのいずれを使用しているかに関係なく、機能します。Winform と WPF はどちらも適切な機能を提供し、インストールします。 SynchronizationContext
メイン/UI スレッド用。
特定のスレッドでデリゲートを呼び出す一般的な手順は次のとおりです。
ターゲット スレッドの (Zさん)
SynchronizationContext
, 、できるようにSend
(同期的に) またはPost
(非同期的に) そのスレッドへのデリゲート。これを行う方法は、によって返された同期コンテキストを保存することです。SynchronizationContext.Current
対象のスレッドにいる間 Z. 。(この同期コンテキストは、スレッド上またはスレッドによって事前に登録されている必要があります Z.) 次に、その参照をスレッドからアクセスできる場所に保存します。 あ.スレッドにいる間 あ, 、キャプチャされた同期コンテキストを使用して、スレッドにデリゲートを送信または投稿できます。 Z:
zSyncContext.Post(_ => { ... }, null);
他のヒント
メッセージ ループがないスレッドでのデリゲートの呼び出しをサポートしたい場合は、基本的に独自の実装を行う必要があります。
メッセージ ループには特に魔法のようなものはありません。これは、通常の生産者/消費者パターンの消費者と同じです。実行すべきこと (通常は反応するイベント) のキューを保持し、それに応じて動作するキューを通過します。何もすることが残っていない場合は、キューに何かが入れられるまで待機します。
別の言い方をすると:メッセージ ループを持つスレッドは、単一スレッドのスレッド プールと考えることができます。
これは、コンソール アプリなどで自分で簡単に実装できます。スレッドがワーク キューをループしている場合、他のことを同時に行うことはできないことに注意してください。一方、通常、コンソール アプリの実行のメイン スレッドは、一連のタスクを実行して終了することを目的としています。
.NET 4 を使用している場合、次を使用してプロデューサー/コンシューマー キューを実装するのは非常に簡単です。 BlockingCollection
クラス。
最近この記事を見つけて、命の恩人であることがわかりました。上記の Jon Skeet 氏が指摘したように、ブロッキング同時キューの使用が秘密のソースです。このすべての作業を行う上で私が見つけた最高の「ハウツー」は次のとおりです。 この記事 Mike Peretz による CodeProject について。この記事は、SynchronizationContext に関する 3 部構成のシリーズの一部であり、製品コードに簡単に変換できるコード例を提供します。Peretz はすべての詳細を記入しているだけであることに注意してください。また、基本 SynchronizationContext には Post() と Send() の本質的に価値のない実装があるため、実際には抽象基本クラスとして見なすべきであることも思い出させてくれます。基本クラスをカジュアルに使用する人は、それが現実世界の問題を解決しないことに驚くかもしれません。