7

SynchronizationContext与えられたものを取得する方法が見つからないようですThread

Thread uiThread = UIConfiguration.UIThread;
SynchronizationContext context = uiThread.Huh?;

なぜそれが必要なのですか?

フロントエンドアプリケーション全体のさまざまな場所からUIThreadに投稿する必要があるためです。そこで、。というクラスで静的プロパティを定義しましたUIConfiguration。このプロパティをProgram.Mainメソッドで設定します。

UIConfiguration.UIThread = Thread.CurrentThread;

その瞬間、正しいスレッドがあることを確認できますが、次のような静的プロパティを設定することはできません。

UIConfiguration.SynchronizationContext = SynchronizationContext.Current

そのクラスのWinForms実装はまだインストールされていないためです。Thread各スレッドには独自のSynchronizationContextがあるため、特定のオブジェクトからスレッドを取得できる必要がありますか、それとも完全に間違っていますか?

4

5 に答える 5

14

これは不可能です。問題は、aSynchronizationContextとaThreadが実際には2つの完全に別個の概念であるということです。

WindowsフォームとWPFの両方SynchronizationContextがメインスレッド用にセットアップするのは事実ですが、他のほとんどのスレッドはセットアップしません。たとえば、ThreadPool内のどのスレッドにも独自のSynchronizationContextが含まれていません(もちろん、独自のスレッドをインストールしない限り)。

SynchronizationContextがスレッドやスレッドと完全に無関係である可能性もあります。外部サービスやスレッドプール全体などに同期する同期コンテキストを簡単に設定できます。

あなたの場合、UIConfiguration.SynchronizationContext最初のメインフォームのLoadedイベント内に設定することをお勧めします。コンテキストはその時点で開始されることが保証されており、いずれの場合もメッセージポンプが開始されるまで使用できません。

于 2010-11-05T15:59:12.640 に答える
7

これは古い質問であり、ネクロについてお詫びしますが、これをグーグルしている私たちにとって役立つかもしれないと思ったこの問題の解決策を見つけました(そしてそれはControlインスタンスを必要としません)。

基本的に、次のように、WindowsFormsSynchronizationContextのインスタンスを作成し、Main関数でコンテキストを手動で設定できます。

    _UISyncContext = new WindowsFormsSynchronizationContext();
    SynchronizationContext.SetSynchronizationContext(_UISyncContext);

私は自分のアプリケーションでこれを実行しましたが、問題なく完全に機能します。ただし、私はSTAThreadでマークされていることを指摘する必要があります。そのため、代わりにMTAThreadでマークされているMain場合でも、これが機能するかどうか(または必要かどうか)はわかりません。Main

編集:私はそれについて言及するのを忘れましたが、私のアプリケーションのクラス_UISyncContextのモジュールレベルですでに定義されています。Program

于 2011-04-17T12:11:42.407 に答える
7

Alex Daviesの著書「AsyncinC#5.0。O'Reilly Publ。、2012」、p.48-49から、次の文章が最も簡潔で参考になったと思います。

  • SynchronizationContextは.NETFrameworkによって提供されるクラスであり、特定の種類のスレッドでコードを実行する機能があります
    。.NETで使用されるさまざまな同期コンテキストがあり、その中で最も重要なのはWinFormsで使用されるUIスレッドコンテキストです。 WPF。」

  • 「インスタンスSynchronizationContext自体はあまり有用ではないため、実際のインスタンスはすべてサブクラスになる傾向があります。

    また、現在のを読み取って制御できる静的メンバーもありますSynchronizationContext

    currentSynchronizationContextは、現在のスレッドのプロパティです。

    SynchronizationContext特別なスレッドで実行しているどの時点でも、現在のスレッドを取得して保存できる必要があるという考え方です。後で、それを使用して、開始した特別なスレッドでコードを実行し直すことができます。これはすべて、開始したスレッドを正確に知る必要がなくても可能であるはずです。SynchronizationContextを使用できる限り、そのスレッドに戻ることができます

    SynchronizationContextの重要なメソッドはPost、デリゲートを適切なコンテキストで実行できるようにすること
    です。

  • 一部のSynchronizationContextは、UIスレッドのように単一のスレッドをカプセル化します
    一部は、特定の種類のスレッド(たとえば、スレッドプール)をカプセル化しますが、デリゲートを投稿するスレッドを選択できます。実際には、どのスレッドを変更しないものもあります。コードは実行されますが、ASP.NET同期コンテキストのように監視にのみ使用されます。」

于 2013-04-29T02:11:46.263 に答える
2

すべてのスレッドに独自のスレッドあるとは思いません。SynchronizationContextスレッドローカルだけSynchronizationContextです。

UIConfiguration.UIThreadフォームなどのイベントで設定してみませんLoadedか?

于 2010-11-05T15:48:22.623 に答える
1

から(または存在しない場合)、またはからSynchronizationContextを取得するための完全で機能する拡張メソッド。.NET4.6.2でテスト済みThreadExecutionContextnullDispatcherSynchronizationContextDispatcher

using Ectx = ExecutionContext;
using Sctx = SynchronizationContext;
using Dctx = DispatcherSynchronizationContext;

public static class _ext
{
    // DispatcherSynchronizationContext from Dispatcher
    public static Dctx GetSyncCtx(this Dispatcher d) => d?.Thread.GetSyncCtx() as Dctx;

    // SynchronizationContext from Thread
    public static Sctx GetSyncCtx(this Thread th) => th?.ExecutionContext?.GetSyncCtx();

    // SynchronizationContext from ExecutionContext
    public static Sctx GetSyncCtx(this Ectx x) => __get(x);

    /* ... continued below ... */
}

__get上記のすべての関数は、最終的に以下に示すコードを呼び出すことになります。これには、説明が必要です。

これ__getは静的フィールドであり、破棄可能なラムダブロックで事前に初期化されていることに注意してください。これにより、1回限りの初期化を実行するために、最初の呼び出し元のみを適切にインターセプトできます。これにより、はるかに高速でリフレクションのない、小さくて永続的な置換デリゲートが準備されます。

勇敢な初期化作業の最後の行動は、置換を'__get'にスワップすることです。これは、コードがそれ自体を破棄し、トレースを残さないことを同時に悲劇的に意味し、後続のすべての呼び出し元は、バイパスロジックのヒントさえもなしに直接DynamicMethod適切に進みます。

static Func<Ectx, Sctx> __get = arg =>
{
    // Hijack the first caller to do initialization...

    var fi = typeof(Ectx).GetField(
        "_syncContext",                         // private field in 'ExecutionContext'
        BindingFlags.NonPublic|BindingFlags.Instance);

    var dm = new DynamicMethod(
        "foo",                                  // (any name)
        typeof(Sctx),                           // getter return type
        new[] { typeof(Ectx) },                 // type of getter's single arg
        typeof(Ectx),                           // "owner" type
        true);                                  // allow private field access

    var il = dm.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldfld, fi);
    il.Emit(OpCodes.Ret);

    // ...now replace ourself...
    __get = (Func<Ectx, Sctx>)dm.CreateDelegate(typeof(Func<Ectx, Sctx>));

    // oh yeah, don't forget to handle the first caller's request
    return __get(arg);                 //  ...never to come back here again. SAD!
};

かわいい部分は、プリエンプトされた最初の呼び出し元の値を実際にフェッチするために、関数が表面上は独自の引数で自分自身を呼び出すが、直前に自分自身を置き換えることによって再発を回避する最後の部分です。

SynchronizationContextこのページで議論されている特定の問題について、この珍しいテクニックを示す特別な理由はありません。から_syncContextフィールドを取得するExecutionContextことは、従来の反射(およびいくつかの拡張方法のフロスティング)を使用して簡単かつ簡単に対処できます。しかし、私が個人的にかなり長い間使用してきたこのアプローチを共有したいと思いました。なぜなら、それは簡単に適応でき、そのような場合にも同じように広く適用できるからです。

非公開フィールドにアクセスする際に極端なパフォーマンスが必要な場合に特に適しています。私はもともとこれをQPCベースの周波数カウンターで使用したと思います。この周波数カウンターでは、フィールドが20または25ナノ秒ごとに繰り返されるタイトなループで読み取られました。これは、従来の反射では実際には不可能でした。

これで主な答えは終わりですが、以下に、質問者の質問とはあまり関係のない興味深い点をいくつか紹介しました。

 


ランタイム発信者

わかりやすくするために、上記のコードでは、「インストールスワップ」と「最初の使用」の手順を、自分のコードにあるものとは対照的に、2つの別々の行に分けました(次のバージョンでも、前のバージョンと比較して1つのメインメモリフェッチが回避されます) 、スレッドセーフに関係している可能性があります。以下の詳細な説明を参照してください):

return (__get = (Func<Ectx, Sctx>)dm.CreateDel...(...))(arg);

つまり、最初の呼び出し元を含むすべての呼び出し元がまったく同じ方法で値をフェッチし、そのためにリフレクションコードが使用されることはありません。置換ゲッターのみを書き込みます。il-visualizerの好意によりDynamicMethod、実行時にデバッガーでその本体を確認できます。

ldarg.0 <br> ldfld SynchronizationContext _syncContext / ExecutionContext <br> ret

ロックフリースレッドセーフ

関数本体でのスワッピングは、.NETメモリモデルとロックフリーの哲学を考えると、完全にスレッドセーフな操作であることに注意してください。後者は、重複または冗長な作業を行うことを犠牲にして、前向きな保証を支持します。初期化するマルチウェイレースは、完全に健全な理論的根拠に基づいて正しく許可されています。

  • レースエントリポイント(初期化コード)はグローバルに事前構成され、(.NETローダーによって)保護されているため、(複数の)レーサー(存在する場合)が同じ初期化子に入ることができますnull
  • 複数のレース製品(ゲッター)は常に論理的に同一であるため、特定のレーサー(または後で非レースの発信者)がたまたまどのレーサーをピックアップするか、またはレーサーが自分で作成したものを使用するかどうかは関係ありません。
  • 各インストールスワップはサイズの単一ストアでありIntPtr、それぞれのプラットフォームのビット数に対してアトミックであることが保証されています。
  • 最後に、正式な正しさを完全にするために技術的に絶対的に重要なのは、「敗者」の作業成果物が回収されるGCため、リークが発生しないことです。このタイプのレースでは、敗者は最後のフィニッシャーを除くすべてのレーサーです(他のすべての人の努力は、同じ結果で簡単にそして要約的に上書きされるため)。

これらの点が組み合わさって、考えられるあらゆる状況下で記述されたコードを完全に保護すると思いますが、それでも全体的な結論に疑問がある場合や警戒している場合は、いつでも防弾の層を追加できます。

var tmp = (Func<Ectx, Sctx>)dm.CreateDelegate(typeof(Func<Ectx, Sctx>));
Thread.MemoryBarrier();
__get = tmp;
return tmp(arg);

単なるパラニオドバージョンです。以前の凝縮されたワンライナーと同様に、.NETメモリモデルは、'__get'の場所に正確に1つのストアがあり、フェッチがゼロであることを保証します。(上部の完全に拡張された例は、追加のメインメモリフェッチを実行しますが、2番目の箇条書きのおかげでまだ健全です)前述したように、これは正確さのために必要ではありませんが、理論的には、ごくわずかです。パフォーマンスボーナス:最終的にレースを早期に終了することにより、非常にまれなケースで、ダーティキャッシュライン上の後続の呼び出し元が不必要に(ただし無害に)レースを行うのを防ぐことができます。

ダブルサンク

最後の超高速メソッドへの呼び出しは、前に示した静的拡張メソッドを介してサンクされます。これは、コンパイラがメタデータにバインドして伝播するために、コンパイル時に実際に存在するエントリポイントも何らかの方法で表す必要があるためです。ダブルサンクは、実行時まで実際に解決できないカスタマイズされたコードのIDEで、強く型付けされたメタデータとインテリセンスの圧倒的な利便性を支払うための小さな代償です。それでも、少なくとも静的にコンパイルされたコードと同じくらい高速に実行されすべての呼び出しで大量のリフレクションを実行するよりもはるかに高速であるため、両方の長所を活用できます。

于 2017-07-08T12:59:54.127 に答える