プログラミングC#の本には、SynchronizationContext
に関するサンプルコードがあります。
SynchronizationContext originalContext = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(delegate {
string text = File.ReadAllText(@"c:\temp\log.txt");
originalContext.Post(delegate {
myTextBox.Text = text;
}, null);
});
私はスレッドの初心者なので、詳しく答えてください。まず、コンテキストの意味がわかりません。プログラムはoriginalContext
に何を保存しますか? Post
メソッドが起動されると、UIスレッドは何をしますか?
おかしなことを聞いたら、訂正してください、ありがとう!
編集:たとえば、単にmyTextBox.Text = text;
メソッドでは、違いは何ですか?
SynchronizationContextは何をしますか?
簡単に言えば、 SynchronizationContext
は、コードが実行される可能性のある場所を表します。 Send
または Post
メソッド に渡されたデリゲートは、その場所で呼び出されます。 (Post
は、Send
の非ブロッキング/非同期バージョンです。)
すべてのスレッドにSynchronizationContext
インスタンスを関連付けることができます。 static SynchronizationContext.SetSynchronizationContext
メソッド を呼び出すことにより、実行中のスレッドを同期コンテキストに関連付けることができ、実行中のスレッドの現在のコンテキストを SynchronizationContext.Current
プロパティ で照会できます。
私が書いたばかり(各スレッドに関連する同期コンテキストがある)にもかかわらず、SynchronizationContext
は必ずしも特定のスレッドを表すとは限りません。また、渡されたデリゲートの呼び出しをいくつかのスレッド(たとえば、 ThreadPool
ワーカースレッド)、または(少なくとも理論的には)特定のCPUコアに転送することもできます。 、または別のネットワークホストへ。デリゲートが実行される場所は、使用されるSynchronizationContext
のタイプによって異なります。
Windows Formsは、最初のフォームが作成されたスレッドにWindowsFormsSynchronizationContext
をインストールします。 (このスレッドは一般に「UIスレッド」と呼ばれます。)このタイプの同期コンテキストは、そのスレッドで渡されたデリゲートを呼び出します。 Windowsフォームは、他の多くのUIフレームワークと同様に、作成された同じスレッド上でのみコントロールの操作を許可するため、これは非常に便利です。
メソッドに
myTextBox.Text = text;
とだけ書いた場合、違いは何ですか?
ThreadPool.QueueUserWorkItem
に渡したコードは、スレッドプールワーカースレッドで実行されます。つまり、myTextBox
が作成されたスレッドでは実行されないため、遅かれ早かれ(特にリリースビルドで)Windowsフォームは例外をスローし、別のスレッドからmyTextBox
にアクセスできないことを通知します。
そのため、特定の割り当ての前に、何らかの方法でワーカースレッドから「UIスレッド」(myTextBox
が作成された場所)に「スイッチバック」する必要があります。これは次のように行われます。
まだUIスレッドにいる間に、そこにWindows FormsのSynchronizationContext
をキャプチャし、後で使用するために変数への参照を保存します(originalContext
)。この時点でSynchronizationContext.Current
をクエリする必要があります。 ThreadPool.QueueUserWorkItem
に渡されたコード内でクエリを実行すると、スレッドプールのワーカースレッドに関連付けられている同期コンテキストを取得できます。 Windowsフォームのコンテキストへの参照を保存したら、いつでもどこでも使用して、UIスレッドにコードを「送信」できます。
UI要素を操作する必要がある場合(UIスレッド上ではなく、またはUIスレッド上にない場合)は、originalContext
を介してWindows Formsの同期コンテキストにアクセスし、UIを操作するコードをSend
またはPost
に渡します。
同期コンテキストががしないことは、特定の場所/コンテキストで実行する必要があるコードと、SynchronizationContext
に渡さずに正常に実行できるコードを示しています。それを決定するには、プログラミング対象のフレームワーク(この場合はWindowsフォーム)のルールと要件を知っている必要があります。
したがって、Windowsフォームのこの単純なルールを覚えておいてください。コントロールまたはフォームに、それらを作成したスレッド以外のスレッドからアクセスしないでください。これを行う必要がある場合は、上記のSynchronizationContext
メカニズム、または Control.BeginInvoke
(まったく同じことを行うWindowsフォーム固有の方法)を使用します。
.NET 4.5以降を使用してプログラミングしている場合は、SynchronizationContext
、ThreadPool.QueueUserWorkItem
、control.BeginInvoke
などを明示的に使用するコードを新しい async
に変換することで、作業を大幅に楽にできます。 await
キーワード および Task Parallel Library(TPL) 、つまり Task
および Task<TResult>
クラスを囲むAPI。これらは、非常に高度に、UIスレッドの同期コンテキストをキャプチャし、非同期操作を開始してから、操作の結果を処理できるようにUIスレッドに戻ります。
他の回答に追加したい、SynchronizationContext.Post
は、ターゲットスレッドでの実行(通常はターゲットスレッドのメッセージループの次のサイクル)のためにコールバックをキューに入れ、呼び出しスレッドで実行を継続します。一方、 SynchronizationContext.Send
は、ターゲットスレッドでコールバックをすぐに実行しようとします。これにより、呼び出し元のスレッドがブロックされ、デッドロックが発生する可能性があります。どちらの場合も、コードの再入可能性があります(同じメソッドへの前回の呼び出しが戻る前に、同じ実行スレッドでクラスメソッドを入力します)。
Win32プログラミングモデルに精通している場合は、PostMessage
およびSendMessage
APIに非常に近い類似性があります。これらのAPIを呼び出して、ターゲットウィンドウのスレッドとは異なるスレッドからメッセージをディスパッチできます。
以下に、同期コンテキストとは何かについての非常に良い説明があります。 SynchronizationContextのすべて 。
SynchronizationContextから派生したクラスである同期プロバイダーを格納します。この場合、おそらくWindowsFormsSynchronizationContextのインスタンスになります。そのクラスは、Control.Invoke()およびControl.BeginInvoke()メソッドを使用して、Send()およびPost()メソッドを実装します。または、DispatcherSynchronizationContextにすることができ、Dispatcher.Invoke()およびBeginInvoke()を使用します。 WinformsまたはWPFアプリでは、ウィンドウを作成するとすぐにそのプロバイダーが自動的にインストールされます。
スニペットで使用されるスレッドプールスレッドなど、別のスレッドでコードを実行する場合、スレッドセーフでないオブジェクトを直接使用しないように注意する必要があります。他のユーザーインターフェイスオブジェクトと同様に、TextBoxを作成したスレッドからTextBox.Textプロパティを更新する必要があります。 Post()メソッドは、デリゲートターゲットがそのスレッドで実行されるようにします。
このスニペットは少し危険であり、UIスレッドから呼び出すときにのみ正しく機能することに注意してください。 SynchronizationContext.Currentには、スレッドごとに異なる値があります。 UIスレッドのみに使用可能な値があります。そして、コードがそれをコピーしなければならなかった理由です。 Winformsアプリでより読みやすく安全な方法:
ThreadPool.QueueUserWorkItem(delegate {
string text = File.ReadAllText(@"c:\temp\log.txt");
myTextBox.BeginInvoke(new Action(() => {
myTextBox.Text = text;
}));
});
これには、anyスレッドから呼び出されたときに機能するという利点があります。 SynchronizationContext.Currentを使用する利点は、コードがWinformsで使用されるかWPFで使用されるかに関わらず、ライブラリで重要であることです。これは確かにnotこのようなコードの良い例です。ここにあるTextBoxの種類を常に知っているので、Control.BeginInvokeとDispatcher.BeginInvokeのどちらを使用するかを常に知っています。実際にSynchronizationContext.Currentを使用することはそれほど一般的ではありません。
この本はスレッド化についてあなたに教えようとしているので、この欠陥のある例の使用は大丈夫です。実際には、mightSynchronizationContext.Currentの使用を検討するいくつかのケースでは、C#のasync/awaitキーワードまたはTaskSchedulerに任せることができます。 .FromCurrentSynchronizationContext()を実行してください。ただし、まったく同じ理由で、間違ったスレッドでスニペットを使用した場合のスニペットの振る舞いは依然として正しくないことに注意してください。ここで非常によくある質問である、抽象化レベルの追加は有用ですが、なぜそれらが正しく動作しないのかを理解するのが難しくなります。願わくば、この本がいつ使わないかを教えてくれることを願っています:)
ここでの同期コンテキストの目的は、myTextbox.Text = text;
は、メインUIスレッドで呼び出されます。
Windowsでは、GUIコントロールが作成されたスレッドによってのみアクセスされることが必要です。最初に同期せずに(このパターンやInvokeパターンなどのいくつかの方法で)バックグラウンドスレッドでテキストを割り当てようとすると、例外がスローされます。
これは、バックグラウンドスレッドを作成する前に同期コンテキストを保存し、バックグラウンドスレッドがcontext.Postメソッドを使用してGUIコードを実行することです。
はい、あなたが示したコードは基本的に役に立ちません。バックグラウンドスレッドを作成し、すぐにメインUIスレッドに戻る必要があるのはなぜですか?これは単なる例です。
SynchronizationContextは、異なるスレッドからUIを更新する方法を提供します(Sendメソッドを介して同期的に、またはPostメソッドを介して非同期的に)。
次の例を見てください。
private void SynchronizationContext SyncContext = SynchronizationContext.Current;
private void Button_Click(object sender, RoutedEventArgs e)
{
Thread thread = new Thread(Work1);
thread.Start(SyncContext);
}
private void Work1(object state)
{
SynchronizationContext syncContext = state as SynchronizationContext;
syncContext.Post(UpdateTextBox, syncContext);
}
private void UpdateTextBox(object state)
{
Thread.Sleep(1000);
string text = File.ReadAllText(@"c:\temp\log.txt");
myTextBox.Text = text;
}
SynchronizationContext.Currentは、UIスレッドの同期コンテキストを返します。これをどうやって知るのですか?すべてのフォームまたはWPFアプリの開始時に、コンテキストがUIスレッドに設定されます。 WPFアプリを作成して例を実行すると、ボタンをクリックすると、約1秒間スリープし、ファイルのコンテンツが表示されることがわかります。 UpdateTextBoxメソッド(Work1)の呼び出し元はThreadに渡されるメソッドであるため、期待しないかもしれません。したがって、メインUIスレッドではなく、そのスレッドをスリープさせる必要があります。 Work1メソッドはスレッドに渡されますが、SyncContextであるオブジェクトも受け入れることに注意してください。これを見ると、UpdateTextBoxメソッドがWork1メソッドではなくsyncContext.Postメソッドを介して実行されていることがわかります。以下をご覧ください。
private void Button_Click(object sender, RoutedEventArgs e)
{
Thread.Sleep(1000);
string text = File.ReadAllText(@"c:\temp\log.txt");
myTextBox.Text = text;
}
最後の例とこれは同じことを実行します。両方とも、ジョブを実行している間UIをブロックしません。
結論として、SynchronizationContextをスレッドと考えてください。これはスレッドではなく、スレッドを定義します(すべてのスレッドがSyncContextを持っているわけではないことに注意してください)。 UIを更新するためにPostメソッドまたはSendメソッドを呼び出すときはいつでも、メインUIスレッドから通常どおりUIを更新するようなものです。何らかの理由で、別のスレッドからUIを更新する必要がある場合、そのスレッドにメインUIスレッドのSyncContextがあることを確認し、実行するメソッドでSendまたはPostメソッドを呼び出すだけで、すべてが完了します。セットする。
これがお役に立てば幸いです!
すべてのスレッドにはコンテキストが関連付けられています(これは「現在の」コンテキストとも呼ばれます)。これらのコンテキストはスレッド間で共有できます。 ExecutionContextには、プログラムが実行されている現在の環境またはコンテキストの関連メタデータが含まれています。 SynchronizationContextは抽象化を表します。これは、アプリケーションのコードが実行される場所を示します。
SynchronizationContextを使用すると、タスクを別のコンテキストのキューに入れることができます。すべてのスレッドが独自のSynchronizatonContextを持つことができることに注意してください。
例:Thread1とThread2の2つのスレッドがあるとします。 Thread1が何らかの作業を行っており、Thread1がThread2でコードを実行したいとしているとします。可能な方法の1つは、Thread2にSynchronizationContextオブジェクトを要求してThread1に渡し、Thread1がSynchronizationContext.Sendを呼び出してThread2でコードを実行することです。
この例は、Joseph AlbahariのLinqpadの例からのものですが、Synchronizationコンテキストの機能を理解するのに非常に役立ちます。
void WaitForTwoSecondsAsync (Action continuation)
{
continuation.Dump();
var syncContext = AsyncOperationManager.SynchronizationContext;
new Timer (_ => syncContext.Post (o => continuation(), _)).Change (2000, -1);
}
void Main()
{
Util.CreateSynchronizationContext();
("Waiting on thread " + Thread.CurrentThread.ManagedThreadId).Dump();
for (int i = 0; i < 10; i++)
WaitForTwoSecondsAsync (() => ("Done on thread " + Thread.CurrentThread.ManagedThreadId).Dump());
}
SynchronizationContext基本的にコールバックデリゲート実行のプロバイダーであり、コードの特定の部分(タスクにカプセル化された後、デリゲートが所定の実行コンテキストで実行されることを主に担当しますプログラムの.Net TPLのobj)は実行を完了しました。
技術的な観点から、SCは、タスク並列ライブラリオブジェクト専用の機能をサポートおよび提供するように設計されたシンプルなC#クラスです。
コンソールアプリケーションを除くすべての.Netアプリケーションには、WPF、WindowsForm、Asp Net、Silverlightなどの特定の基盤フレームワークに基づいて、このクラスの特定の実装があります。
このオブジェクトの重要性は、コードの非同期実行から返される結果と、非同期作業の結果を待機している依存コードの実行との間の同期にバインドされています。
また、「コンテキスト」という単語は実行コンテキスト、つまり、待機コードが実行される現在の実行コンテキスト、つまり非同期コードとその待機コード間の同期が特定の実行コンテキストで発生するため、このオブジェクトはSynchronizationContextと呼ばれます。 )これは、非同期コードの同期と待機中のコード実行の監視を行う実行コンテキストを表します。