21

.NET の非同期機能を少しいじっていたところ、うまく説明できない状況に遭遇しました。同期 ASP.NET MVC コントローラー内で次のコードを実行する場合

var t = Task.Factory.StartNew(()=>{
        var ctx = System.Web.HttpContext.Current;
        //ctx == null here
},
    CancellationToken.None,
    TaskCreationOptions.None,
    TaskScheduler.FromCurrentSynchronizationContext()
);

t.Wait();

ctxnullデリゲート内にあります。私の理解では、TaskScheduler.FromCurrentSynchronizationContext()タスクスケジューラを使用するときにコンテキストを復元する必要があります。では、なぜここにいないのでしょうか。(ところで、デリゲートが同じスレッドで同期的に実行されることがわかります)。

また、msdnから、 aTaskScheduler.FromCurrentSynchronizationContext()は次のように動作する必要があります。

返されたスケジューラのキューに入れられたすべての Task インスタンスは、そのコンテキストで Post メソッドを呼び出すことによって実行されます。

ただし、このコードを使用すると:

var wh = new AutoResetEvent(false);

SynchronizationContext.Current.Post(s=> {
    var ctx = System.Web.HttpContext.Current;
    //ctx is set here
    wh.Set();
    return;
},null);

wh.WaitOne();

コンテキストは実際に設定されています。

この例が少し不自然であることは承知していますが、.NET での非同期プログラミングの理解を深めるために何が起こるかを理解したいと思っています。

4

3 に答える 3

8

あなたの観察は正しいようです、それは少し不可解です。 スケジューラを「TaskScheduler.FromCurrentSynchronizationContext()」として指定します。これにより、新しい「SynchronizationContextTaskScheduler」が関連付けられます。このクラスを調べると、次のものが使用されます。 ここに画像の説明を入力してください

したがって、タスクスケジューラが同じ「同期コンテキスト」にアクセスでき、「LegacyAspNetSychronizationContext」を参照する必要がある場合。したがって、確かにHttpContext.currentはnullであってはならないようです。

2番目のケースでは、SychronizationContext(MSDNの記事を参照)を使用すると、スレッドのコンテキストがタスクと共有されます。

「SynchronizationContextのもう1つの側面は、すべてのスレッドに「現在の」コンテキストがあることです。スレッドのコンテキストは必ずしも一意ではありません。そのコンテキストインスタンスは他のスレッドと共有される場合があります。」


この場合、SynchronizationContext.CurrentはLegacyAspNetSychronizationContextによって提供され、内部的にHttpApplicationへの参照があります。

Postメソッドが登録済みのコールバックを呼び出す必要がある場合、HttpApplication.OnThreadEnterを呼び出します。これにより、最終的に現在のスレッドのコンテキストがHttpCurrent.Contextとして設定されます。

ここに画像の説明を入力してください

ここで参照されているすべてのクラスは、フレームワークの内部として定義されており、さらに調査するのが少し難しくなっています。

PS:両方のSynchornizationContextオブジェクトが実際に「LegacyAspNetSynchronizationContext」を指していることを示しています。 ここに画像の説明を入力してください

于 2012-12-31T05:23:01.830 に答える
1

あなたの結果は奇妙です - 本当に他に何も起こっていないのですか?


最初の例 ( with )は、タスク本体を「インライン」で実行できるTaskためのみ機能します。Task.Wait()

タスク ラムダにブレークポイントを配置してコール スタックを見ると、ラムダがTask.Wait()メソッド内から呼び出されていることがわかります。同時実行性はありません。タスクは通常の同期メソッド呼び出しだけで実行されるためHttpContext.Current、コントローラー メソッドの他の場所からの場合と同じ値を返す必要があります。


2 番目の例 ( with SynchronizationContext.Post) はデッドロックになり、ラムダは決して実行されません。

これは、AutoResetEventについて何も「認識」していないを使用しているためですPost。への呼び出しは、 が にWaitOne()なるまでスレッドをブロックしAutoResetEventますSet。同時に、SynchronizationContextラムダを実行するためにスレッドが解放されるのを待っています。

スレッドは でブロックされているWaitOneため、ポストされたラムダは決して実行されません。AutoResetEventつまり、 が設定されず、WaitOneが満たされないことを意味します。これはデッドロックです。

于 2012-12-29T10:54:04.367 に答える