2017-04-24 17 views
7

私はASP.NET 4.6をターゲットとしたASP.NETアプリケーションを持っていましたが、私の非同期MVCコントローラ内で初めてHttpContext.Currentがnullになる理由を突き止めようとしていますアクション。ASP.NET 4.6非同期コントローラメソッドがHttpContext.Currentを失うのを待ちました

自分のプロジェクトがv4.6をターゲットにしており、web.configのtargetFramework属性も4.6であることを確認してトリプルチェックしました。

SynchronizationContext.Currentは、awaitの前後に割り当てられます。これは適切なものです。つまり、従来のものではなくAspNetSynchronizationContextです。

FWIWは、外部I/Oバウンドコード(非同期データベースコール)を呼び出すが、AFAIUは問題ではないという事実のために、スレッドの継続を継続しています。

それでも! HttpContext.Currentがヌルになるという事実は、私のコードのためにいくつかの問題を引き起こし、私には意味をなさない。

私はthe usual recommendationsをチェックしました。私は肯定的です。私はすべきことすべてをやっています。私も絶対にConfigureAwaitが私のコードにありません!私は何をすべきか

は、私のHttpApplicationインスタンス上の非同期イベントハンドラのカップルです:

public MvcApplication() 
{ 
    var helper = new EventHandlerTaskAsyncHelper(Application_PreRequestHandlerExecuteAsync); 
    AddOnPreRequestHandlerExecuteAsync(helper.BeginEventHandler, helper.EndEventHandler); 

    helper = new EventHandlerTaskAsyncHelper(Application_PostRequestHandlerExecuteAsync); 
    AddOnPostRequestHandlerExecuteAsync(helper.BeginEventHandler, helper.EndEventHandler); 
} 

ため、私は非同期を必要とするカスタム認証&クリーンアップ・ロジック、これらの2が必要です。 AFAIU、これはサポートされており、問題ではありません。

他に、私が見ているこの困惑する行動の理由は他にありますか?

更新:追加の観察。

SynchronizationContextは、待ってから待つ前と同じにとどまります。しかし、以下のスクリーンショットに見られるように、内部が変化しています! AWAIT、BEFORE

Before entering await

AWAIT AFTER: After continuing

私は、これは、この時点で私の問題に関連するかもしれない方法(あるいはたとえ)わかりません。うまくいけば誰かがそれを見ることができます!

+1

コントローラのアクションで 'ConfigureAwait(false)'を呼び出していますか? –

+0

@Paulo:いいえ、私はそうではありません。 – aoven

+1

@エルドー:そのポイントは何ですか?私は、内部(つまり、私が言及したDB呼び出し)が非同期であるため、コントローラのアクションで非同期が必要であり、非同期であることが必要です。非同期を削除すると、膨大な量のコードを無効にする必要があります。 – aoven

答えて

0

私はHttpContext.Currentに腕時計を定義することに決め、正確にそれが変わるところを見るために待っています。驚くことではありませんが、私が行ったときにスレッドが何度も切り替えられました。これは途中で複数の真の非同期呼び出しがあったので意味がありました。彼らはすべて、HttpContext.Currentインスタンスを想定どおりに保存していました。

そして、私は短い説明がplan.ExecuteAsyncは、専用スレッドを経由して、非ブロッキング方式で、特殊なイベントログに報告されているステップ数を実行していることである

var observer = new EventObserver(); 
using (EventMonitor.Instance.Observe(observer, ...)) 
{ 
    await plan.ExecuteAsync(...); 
} 

var events = await observer.Task; // Doh! 

...問題のある行を打ちます。これはビジネスソフトウェアであり、レポートイベントのパターンはコード全体で非常に広く使用されています。ほとんどの場合、これらのイベントは発信者にとって直接の関心事ではありません。しかし、1つか2つの場所は、呼び出し元が特定のコードを実行した結果どのイベントが発生したのかを知りたいという点で特別です。上記のように、EventObserverインスタンスが使用されたときです。

await observer.Taskは、関連するすべてのイベントが処理され監視されるのを待つために必要です。問題のタスクは、オブザーバが所有するTaskCompletionSourceインスタンスから取得されます。すべてのイベントが流入すると、イベントを処理したスレッドからソースのSetResultが呼び出されます。このディテールの私の元の実装でした - 非常に単純 - 次のように:

public class EventObserver : IObserver<T> 
{ 
    private readonly ObservedEvents _events = new ObservedEvents(); 

    private readonly TaskCompletionSource<T> _source; 

    private readonly SynchronizationContext _capturedContext; 

    public EventObserver() 
    { 
     _source = new TaskCompletionSource<T>(); 

     // Capture the current synchronization context. 
     _capturedContext = SynchronizationContext.Current; 
    } 

    void OnCompleted() 
    { 
     // Apply the captured synchronization context. 
     SynchronizationContext.SetSynchronizationContext(_capturedContext); 
     _source.SetResult(...); 
    } 
} 

私は今SetResultSetSynchronizationContextを呼び出すと、私はそれが望んだものをやっていないことがわかります。最終的な目標は、元の同期コンテキストをawait observer.Taskの行に適用することでした。

問題は次のとおりです。これを正しく行うにはどうすればよいですか?私はそれが明示的にContinueWithコールどこかにかかると思います。

UPDATE

は、ここに私がやったことです。私はTaskCreationOptions.RunContinuationsAsynchronouslyオプションTaskCompletionSource ctorのを渡され、明示的に同期継続含めるために私EventObserverクラス上のタスクのプロパティを変更:だから今、コードはawait observer.Taskを呼び出したときに、継続は必ず正しいコンテキストが最初に入力されるようになります

public Task<T> Task 
{ 
    get 
    { 
     return _source.Task.ContinueWith(t => 
     { 
      if (_capturedContext != null) 
      { 
       SynchronizationContext.SetSynchronizationContext(_capturedContext); 
      } 

      return t.Result; 
     }); 
    } 
} 

を。これまでのところ、正しく動作しているようです!

+2

別のオプションは、 'SetResult'への呼び出しを同期化コンテキストにポストすることです。何かのような: '_capturedContext.Post(_ => _source.SetResult(...)、null);' –

関連する問題