2013-05-20 9 views
34

次のコードは、新しく作成したVisual Studio 2012 .NET 4.5 WebAPIプロジェクトに追加されました。Thread.CurrentPrincipalが正しく流れるために、「Wait Task.Yield()」が必要ですか?

HttpContext.Current.UserThread.CurrentPrincipalの両方を非同期方式で割り当てようとしています。 await Task.Yield();(またはその他の非同期のもの)が実行されない限り、Thread.CurrentPrincipalの割り当ては正しく流れません(trueAuthenticateAsync()を成功させる)。

なぜですか?

using System.Security.Principal; 
using System.Threading.Tasks; 
using System.Web.Http; 

namespace ExampleWebApi.Controllers 
{ 
    public class ValuesController : ApiController 
    { 
     public async Task GetAsync() 
     { 
      await AuthenticateAsync(false); 

      if (!(User is MyPrincipal)) 
      { 
       throw new System.Exception("User is incorrect type."); 
      } 
     } 

     private static async Task AuthenticateAsync(bool yield) 
     { 
      if (yield) 
      { 
       // Why is this required? 
       await Task.Yield(); 
      } 

      var principal = new MyPrincipal(); 
      System.Web.HttpContext.Current.User = principal; 
      System.Threading.Thread.CurrentPrincipal = principal; 
     } 

     class MyPrincipal : GenericPrincipal 
     { 
      public MyPrincipal() 
       : base(new GenericIdentity("<name>"), new string[] {}) 
      { 
      } 
     } 
    } 
} 

注:

  • await Task.Yield();AuthenticateAsync()のどこにでも現れることができるか、それがAuthenticateAsync()を呼び出した後GetAsync()に移動することができ、それはまだ成功します。
  • ApiController.Userは、Thread.CurrentPrincipalを返します。
  • HttpContext.Current.Userは、await Task.Yield()がなくても常に正しく流れます。
  • Web.configは、<httpRuntime targetFramework="4.5"/>,impliesUseTaskFriendlySynchronizationContextを含む。
  • 私は偶数日前にa similar questionと尋ねましたが、Task.Delay(1000)が存在していたため、その例が成功しただけであることに気づいていませんでした。
+0

あなたがそれを忘れた場合はどうなりますか? – SLaks

+0

@SLaks 'await Task.Yield()'がスキップされた場合、 'Thread.CurrentPrincipal'は' await AuthenticateAsync() 'を呼び出す前の状態に戻ります。 'Thread.CurrentPrincipal'はもはや' MyPrincipal'ではないので、例外がスローされます。 –

+0

私のOwinミドルウェアでは、単にTask.Yield()を待っている最後のミドルウェアにチェーンを張らなければなりませんでした。 これは、Thread.CurrentPrincipalを実行中に後で期待どおりにするように思えます。 – BrettJ

答えて

37

面白い! Thread.CurrentPrincipalは、論理呼び出しコンテキストに基づいており、スレッドごとの呼び出しコンテキストには基づいていないようです。 IMOこれはまったく直感的ではないので、なぜこのように実装されたのか聞いてみるのが面白いでしょう。 .NET 4.5では


。それはより適切async方法で流れるように、async方法は、論理的なコールコンテキストと対話します。私にはblog post on the topicがあります。 AFAIKはそれが文書化されている唯一の場所です。 .NET 4.5では、すべてのasyncメソッドの初めに、その論理呼び出しコンテキストに対して「書き込み時コピー」動作をアクティブにします。論理呼び出しコンテキストが変更された場合、最初に自身のローカルコピーを作成します。

ウォッチウィンドウでSystem.Threading.Thread.CurrentThread.ExecutionContextBelongsToCurrentScopeを観察すると、論理的なコールコンテキストの「ローカル性」(コピーされているかどうか)がわかります。

Yieldを設定しないと、Thread.CurrentPrincipalを設定すると、論理コールコンテキストのコピーが作成されます。このコンテキストは、そのasyncメソッドに「ローカル」として扱われます。 asyncメソッドが返されると、そのローカルコンテキストは破棄され、元のコンテキストがそのまま使用されます(ExecutionContextBelongsToCurrentScopefalseに戻ります)。

一方、Yieldを実行すると、SynchronizationContextの動作が引き継がれます。実際には、HttpContextがキャプチャされ、両方の方法を再開するために使用されます。この場合、あなたはではありません。がAuthenticateAsyncからGetAsyncに保存されているとみなすと、と表示されます。メソッドが再開される前に実際に起こっているのはHttpContext is preserved, and then HttpContext.User is overwriting Thread.CurrentPrincipalです。

あなたはGetAsyncYieldを移動する場合は、同様の動作を参照してください:ローカル変更はAuthenticateAsyncにスコープとしてThread.CurrentPrincipalが扱われています。そのメソッドが復帰したときに値を戻します。ただし、HttpContext.Userはまだ正しく設定されており、その値はYieldでキャプチャされ、再開するとThread.CurrentPrincipalが上書きされます。

+1

こんにちは!なぜこれがこのように実装されたのか聞いたことがありますか?私はすでにこの記事を3回読んでいますが、それはまだ私の心を吹いています。 – vtortola

+2

@vtortola:私は確かに分かりません。私はそれがユーザーのアクセス許可がバックグラウンドスレッドに自動的に流れるようになったと仮定します。これはおそらく10年前に行われたもので、コピー時の更新時の動作はもっと新しいものです。だから彼らはこの奇妙な方法で葛藤した。 –

関連する問題