SynchronizationContextは何をしますか?


135

『Programming 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;、メソッドに書き込むだけの場合、違いは何ですか?


1
良いマニュアルはこう言っています このクラスによって実装された同期モデルの目的は、共通言語ランタイムの内部非同期/同期操作が異なる同期モデルで適切に動作できるようにすることです。また、このモデルは、さまざまな同期環境で正しく機能するために管理対象アプリケーションが従わなければならない要件の一部を簡素化します。
ta.speot.is 2013

IMHO

7
@RoyiNamir:はい、でも何を推測します:async/ 下にawait依存しSynchronizationContextます。
stakx-2013年

回答:


170

SynchronizationContextは何をしますか?

簡単に言えば、SynchronizationContextコードが実行される可能性のある場所を表します。Sendor Postメソッドに渡されたデリゲートは、その場所で呼び出されます。(Postは、の非ブロッキング/非同期バージョンですSend。)

すべてのスレッドにSynchronizationContextインスタンスを関連付けることができます。実行中のスレッドは、静的SynchronizationContext.SetSynchronizationContextメソッドを呼び出すことで同期コンテキストに関連付けることができ、SynchronizationContext.Currentプロパティを介して実行中のスレッドの現在のコンテキストを照会できます

私が書いたばかり(各スレッドに同期コンテキストが関連付けられている)にもかかわらず、a SynchronizationContextは必ずしも特定のスレッドを表すわけではありません。また、渡されたデリゲートの呼び出しを、いくつかのスレッドのいずれかに(たとえば、ThreadPoolワーカースレッドに)、または(少なくとも理論上は)特定のCPUコアに、または別のネットワークホストに転送することもできます。デリゲートが実行される場所は、SynchronizationContext使用されるタイプによって異なります。

WindowsフォームはWindowsFormsSynchronizationContext、最初のフォームが作成されたスレッドにをインストールします。(このスレッドは一般に「UIスレッド」と呼ばれます。)このタイプの同期コンテキストは、渡されたデリゲートをそのスレッドで呼び出します。Windowsフォームは、他の多くのUIフレームワークと同様に、作成されたのと同じスレッド上のコントロールの操作しか許可しないため、これは非常に便利です。

myTextBox.Text = text;メソッドを記述するだけの場合、違いは何ですか?

渡したコードThreadPool.QueueUserWorkItemは、スレッドプールワーカースレッドで実行されます。つまり、myTextBox作成されたスレッドでは実行されないため、Windowsフォームは遅かれ早かれ(特にリリースビルドでは)例外をスローし、myTextBox別のスレッドからアクセスできないことを通知します。

これがmyTextBox、特定の割り当ての前に、ワーカースレッドから(作成された)「UIスレッド」に何らかの方法で「スイッチバック」する必要がある理由です。これは次のように行われます。

  1. UIスレッドを使用している間に、WindowsフォームをキャプチャSynchronizationContextし、originalContext後で使用できるように、その参照を変数()に保存します。SynchronizationContext.Currentこの時点でクエリを実行する必要があります。に渡されたコード内でクエリを実行した場合ThreadPool.QueueUserWorkItem、スレッドプールのワーカースレッドに関連付けられている同期コンテキストが取得される可能性があります。Windowsフォームのコンテキストへの参照を格納したら、いつでもどこでもそれを使用して、コードをUIスレッドに「送信」できます。

  2. UI要素を操作する必要がある場合(ただし、UIスレッドではないか、そうでない場合もあります)、を介してWindowsフォームの同期コンテキストにアクセスしoriginalContext、UIを操作するコードをSendまたはに渡しますPost


最後の備考とヒント:

  • 同期コンテキストもしないのは、特定の場所/コンテキストで実行する必要があるコードと、それをに渡さずに通常どおりに実行できるコードだけSynchronizationContextです。これを決定するには、プログラミング対象のフレームワーク(この場合はWindowsフォーム)のルールと要件を知っている必要があります。

    したがって、Windowsフォームに関するこの単純な規則を覚えておいてください。コントロールまたはフォームには、それらを作成したスレッド以外のスレッドからアクセスしないでください。これを行う必要がある場合は、SynchronizationContext上記のメカニズムを使用するか、またはControl.BeginInvoke(これはWindowsフォーム固有のまったく同じ方法の方法です)。

  • もし.NET 4.5以降に対してしているプログラミングする場合は、その明示的な使用してコードを変換することによって、あなたの人生ははるかに簡単に作ることができSynchronizationContextThreadPool.QueueUserWorkItemcontrol.BeginInvoke新に、などのオーバーasync/のawaitキーワードタスク並列ライブラリ(TPL) 、周囲すなわちAPI TaskそしてTask<TResult>クラス。これらは、非常に高度に、UIスレッドの同期コンテキストの取得、非同期操作の開始、UIスレッドへの復帰を処理して、操作の結果を処理できるようにします。


あなたが言うWindowsフォームを、他の多くのUIフレームワークのように、唯一の同じスレッド上のコントロールの操作が可能になるが、Windowsのすべてのウィンドウには、それを作成した同じスレッドによってアクセスされなければなりません。
user34660 2017年

4
@ user34660:いいえ、それは正しくありません。Windowsフォームコントロールを作成するスレッドをいくつか持つことができます。ただし、各コントロールはそれを作成した1つのスレッドに関連付けられており、その1つのスレッドのみがアクセスする必要があります。異なるUIスレッドのコントロールも、相互の相互作用が非常に制限されています。一方を他方の親/子にすることはできません。それらの間のデータバインディングは不可能です。など。最後に、コントロールを作成する各スレッドには独自のメッセージが必要です。ループ(Application.RunIIRC によって開始されます)。これはかなり高度なトピックであり、さりげなく行われるものではありません。
stakx-2017年

私の最初のコメントは、「他の多くのUIフレームワークと同様に」一部のウィンドウでは別のスレッドからの「コントロールの操作」が許可されているが、Windowsウィンドウでは許可されていないことを示唆しいるためです。同じウィンドウに対して「Windowsフォームコントロールを作成する複数のスレッドを持つ」ことはできません。「同じスレッドからアクセスする必要がある」と「その1つのスレッドからのみアクセスする必要がある」のは同じことです。同じウィンドウに対して「異なるUIスレッドからのコントロール」を作成することは不可能だと思います。これらすべては、.Net以前のWindowsプログラミングの経験がある私たちにとっては高度なものではありません。
user34660 2017年

3
「ウィンドウ」と「ウィンドウウィンドウ」に関するこのすべての話は、私をかなりめまいにしています。これらの「ウィンドウ」について言及しましたか?私はそうは思いません...
stakx-2017年

1
@ibubi:私はあなたの質問を理解しているとは思いません。スレッドの同期コンテキストは、設定されていない(null)か、インスタンスSynchronizationContext(またはそのサブクラス)ではありません。その引用のポイントは、あなたが得るものはありませんでしたが、何をしません取得:UIスレッドの同期コンテキストを。
stakx-2017年

24

他の回答に追加したいのですがSynchronizationContext.Post、ターゲットスレッドで後で実行するためにコールバックをキューに入れるだけです(通常はターゲットスレッドのメッセージループの次のサイクル中に)。その後、呼び出し側のスレッドで実行が続行されます。一方、SynchronizationContext.Sendターゲットスレッドでコールバックをすぐに実行しようとすると、呼び出しスレッドがブロックされ、デッドロックが発生する可能性があります。どちらの場合も、コードの再入可能性(同じメソッドへの以前の呼び出しが戻る前に、同じ実行スレッドでクラスメソッドに入る)の可能性があります。

Win32プログラミングモデルに精通している場合はPostMessageSendMessageAPI と非常によく似ています。APIを呼び出すと、ターゲットウィンドウとは異なるスレッドからメッセージをディスパッチできます。

次に、同期コンテキストとは何かについての非常に優れた説明を示します。 これは、SynchronizationContextのすべてです。


16

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

これは、任意のスレッドから呼び出されたときに機能するという利点があります。SynchronizationContext.Currentを使用する利点は、コードがWinformsとWPFのどちらで使用されても機能することです。これはライブラリで重要です。これは確かにそのようなコードの良い例ではありません。ここには、どのような種類のTextBoxがあるかを常に知っているので、Control.BeginInvokeまたはDispatcher.BeginInvokeを使用するかどうかを常に知っています。実際にSynchronizationContext.Currentを使用することはそれほど一般的ではありません。

この本はスレッドについて教えようとしているので、この欠陥のある例を使用しても問題ありません。実際には、SynchronizationContext.Currentの使用を検討する可能性のあるいくつかのケースでは、C#のasync / awaitキーワードまたはTaskScheduler.FromCurrentSynchronizationContext()に任せて実行します。ただし、まったく同じ理由で、スニペットを間違ったスレッドで使用すると、スニペットと同じように動作しないことに注意してください。ここで非常に一般的な質問です。抽象化の追加レベルは有用ですが、正しく機能しない理由を理解するのを難しくします。うまくいけば、本はそれを使わないときも教えてくれます:)


申し訳ありませんが、UIスレッドハンドルがスレッドセーフである理由 つまり、Post()が発生したときにUIスレッドがmyTextBoxを使用している可能性があると思いますが、それは安全ですか?
cloudyFan 2013

4
あなたの英語は解読するのが難しいです。オリジナルのスニペットは、UIスレッドから呼び出された場合にのみ正しく機能します。これは非常に一般的なケースです。その後、UIスレッドにポストバックします。ワーカースレッドから呼び出された場合、Post()デリゲートターゲットはスレッドプールスレッドで実行されます。カブーム。これは自分で試してみたいものです。スレッドを開始し、スレッドにこのコードを呼び出させます。コードがNullReferenceExceptionでクラッシュした場合は、正しく実行されました。
Hans Passant 2013

5

ここでの同期コンテキストの目的は、myTextbox.Text = text;メインUIスレッドで呼び出されることを確認することです。

Windowsでは、GUIコントロールは、それが作成されたスレッドによってのみアクセスされる必要があります。最初に同期せずにバックグラウンドスレッドでテキストを割り当てようとすると(これやInvokeパターンなどのいくつかの手段のいずれかを使用して)、例外がスローされます。

これにより、バックグラウンドスレッドを作成する前に同期コンテキストが保存され、バックグラウンドスレッドがcontext.Postメソッドを使用してGUIコードを実行します。

はい、表示したコードは基本的に役に立ちません。なぜバックグラウンドスレッドを作成し、すぐにメインUIスレッドに戻る必要があるのですか?これは単なる例です。


4
「はい、あなたが示したコードは基本的に役に立たないのです。なぜバックグラウンドスレッドを作成し、メインUIスレッドにすぐに戻る必要があるのですか?これは単なる例です。」-ファイルが大きい場合、ファイルからの読み取りは長いタスクになる可能性があり、UIスレッドをブロックして応答しなくなる可能性があります
Yair Nevet

ばかげた質問があります。すべてのスレッドにはIDがあり、たとえばUIスレッドにもID = 2があると思います。次に、スレッドプールスレッドを使用しているときに、そのようなことを実行できますか?var thread = GetThread(2); thread.Execute(()=> textbox1.Text = "foo")?
John

@ジョン-いいえ、スレッドはすでに実行されているので、うまくいきません。すでに実行中のスレッドを実行することはできません。実行は、スレッドが実行されていない場合にのみ機能します(IIRC)
Erik Funkenbusch

3

ソースへ

すべてのスレッドにはコンテキストが関連付けられています。これは「現在の」コンテキストとも呼ばれ、これらのコンテキストはスレッド間で共有できます。ExecutionContextには、プログラムが実行されている現在の環境またはコンテキストの関連メタデータが含まれています。SynchronizationContextは抽象化を表します。これは、アプリケーションのコードが実行される場所を示します。

SynchronizationContextを使用すると、タスクを別のコンテキストにキューイングできます。すべてのスレッドが独自のSynchronizatonContextを持つことができることに注意してください。

例:2つのスレッド、Thread1とThread2があるとします。たとえば、Thread1が何らかの作業を行っており、Thread1がThread2でコードを実行したいとします。これを行う1つの可能な方法は、Thread2にSynchronizationContextオブジェクトを要求し、それをThread1に渡してから、Thread1がSynchronizationContext.Sendを呼び出してThread2でコードを実行することです。


2
同期コンテキストは、必ずしも特定のスレッドに関連付けられているとは限りません。複数のスレッドが単一の同期コンテキストへの要求を処理し、単一のスレッドが複数の同期コンテキストへの要求を処理することが可能です。
サービー2017年

3

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)がスレッドに渡されるメソッドであるため、そうではないかもしれません。そのため、メインの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があるわけではないことに注意してください)。その上でPostまたはSendメソッドを呼び出してUIを更新するときはいつでも、それは通常、メインUIスレッドからUIを更新するのと同じです。何らかの理由で別のスレッドからUIを更新する必要がある場合は、スレッドにメインUIスレッドのSyncContextが含まれていることを確認し、実行するメソッドでそのスレッドのSendまたはPostメソッドを呼び出すだけで、すべて完了です。セットする。

これがあなたを助けることを願っています、仲間!


2

SynchronizationContextは基本的にコールバックデリゲート実行のプロバイダーであり、プログラムの特定のコード部分(.Net TPLのTask objにカプセル化されている)が実行を完了した後、デリゲートが特定の実行コンテキストで実行されることを保証します。

技術的な観点から見ると、SCはシンプルなC#クラスであり、タスク並列ライブラリオブジェクト専用の機能をサポートおよび提供するように方向付けられています。

コンソールアプリケーションを除くすべての.Netアプリケーションには、特定の基本的なフレームワーク(WPF、WindowsForm、Asp Net、Silverlightなど)に基づくこのクラスの特定の実装があります。

このオブジェクトの重要性は、コードの非同期実行から返される結果と、その非同期作業からの結果を待機している依存コードの実行との間の同期に関係しています。

また、「コンテキスト」という単語は実行コンテキストを意味します。つまり、待機コードが実行される現在の実行コンテキストです。つまり、同期コードと非同期コードが特定の実行コンテキストで発生するため、このオブジェクトはSynchronizationContextと呼ばれます。これは、非同期コードの同期化とコード実行の待機を管理する実行コンテキストを表します


1

この例は、Joseph AlbahariのLinqpadの例からのものですが、同期コンテキストの機能を理解するのに役立ちます。

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());
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.