2017-04-20 3 views
3

次のコードの動作について理解していないことがいくつかありますが(主なものは1つあります)await/async同期のコンテキスト切り替え動作の説明方法

誰かがこれを説明できますか?

実際には非常に単純なコードです。非同期メソッドを呼び出す1つの通常のメソッドです。また、非同期メソッドでは、usingブロックを使用してSynchronizationContextを一時的に変更しようとします。

コード内の異なる点で、私は現在のSynchronizationContextを調べます。ここで

は私の質問です:

実行は、 "2.1" は、文脈が コンテキスト#2に変更された位置に到達した
  1. 。はい。次に、 `await`をヒットしたので、Taskは に戻り、実行は1.2の位置にジャンプします。なぜ、 の位置1.2では、コンテキストはコンテキスト2で「スティック」ではないのですか?

    ここでは、usingステートメントと非同期メソッドでいくつかの魔法が行われていますか?
  2. 位置2.2では、なぜコンテキストがコンテキスト2でないのですか?文脈を「継続」(「await」の後の文)に引き継ぐべきではないか?

コード:

public class Test 
    { 
     public void StartHere() 
     { 
      SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); 

      this.logCurrentSyncContext("1.1"); // Context #1 
      Task t = f(); 
      this.logCurrentSyncContext("1.2"); // Context #1, why not Context #2? 
      t.Wait(); 
      this.logCurrentSyncContext("1.3"); // Context #1 
     } 


     private async Task f() 
     { 
      using (new ThreadPoolSynchronizationContextBlock()) 
      { 
       this.logCurrentSyncContext("2.1"); // Context #2 
       await Task.Delay(7000); 
       this.logCurrentSyncContext("2.2"); // Context is NULL, why not Context #2? 
      } 

      this.logCurrentSyncContext("2.3"); // Context #1 
     } 


     // Just show the current Sync Context. Pass in some kind of marker so we know where, in the code, the logging is happening 

     private void logCurrentSyncContext(object marker) 
     { 
      var sc = System.Threading.SynchronizationContext.Current; 
      System.Diagnostics.Debug.WriteLine(marker + " Thread: " + Thread.CurrentThread.ManagedThreadId + " SyncContext: " + (sc == null? "null" : sc.GetHashCode().ToString())); 
     } 

     public class ThreadPoolSynchronizationContextBlock : IDisposable 
     { 
      private static readonly SynchronizationContext threadpoolSC = new SynchronizationContext(); 

      private readonly SynchronizationContext original; 

      public ThreadPoolSynchronizationContextBlock() 
      { 
       this.original = SynchronizationContext.Current; 
       SynchronizationContext.SetSynchronizationContext(threadpoolSC); 
      } 

      public void Dispose() 
      { 
       SynchronizationContext.SetSynchronizationContext(this.original); 
      } 
     } 
    } 

結果:

1.1 Thread: 9 SyncContext: 37121646 // I call this "Context #1" 
2.1 Thread: 9 SyncContext: 2637164 // I call this "Context #2" 
1.2 Thread: 9 SyncContext: 37121646 
2.2 Thread: 11 SyncContext: null 
2.3 Thread: 11 SyncContext: 37121646 
1.3 Thread: 9 SyncContext: 37121646 
+5

2.2:https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,2976 – PetSerAl

+1

1.2:http://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices /AsyncMethodBuilder.cs,320 – PetSerAl

+0

コンテキストブロックがGCの下にあるようです。 'Dispose'通話時間を確認しましたか? – VMAtm

答えて

2

2.2説明が簡単で、1.2は簡単ではありません。 2.2プリントnull

理由は、あなたawaitがデフォルト(new SynchronizationContext)またはnullのSynchronizationContext、Post方法は、このis scheduled on the ThreadPool、継続デリゲートを渡して呼び出されますを使用しているときにあります。現在のインスタンスを復元しようとするのではなく、現在のSynchronizationContextnullであり、これがThreadPool上で実行されている場合は、これらの継続が継続されます。明確にするには、.ConfigureAwait(false)を使用していないため、あなたの期待どおりにキャプチャされたコンテキストに継続がポストされますが、この実装ではPostメソッドは同じインスタンスを保持/フローしません。この問題を解決するには

(すなわち、あなたのコンテキストが「スティッキー」にする)、あなたはSynchronizationContextから継承し、(Delegate.Combine(...)を使用して)掲載デリゲートでSynchronizationContext.SetSynchronizationContext(this)を呼び出すPostメソッドをオーバーロードできます。また、内部ではSynchronizationContextのインスタンスはほとんどの場所でnullと同じ扱いになるため、このようなもので遊びたい場合は、常に継承する実装を作成してください。1.2については

私の理解はこれが(AsyncMethodBuilderからすべての内部と一緒に)基本的なステート・マシンを呼ぶだろうということでしたが、そのSynchronizationContextを維持しながら、それが同期的に呼び出されますように、これは実際には、また、私を驚かせました。

は、私たちはin this postを説明し、ここで見ているものだと思うし、それが捕獲され、これが保護し、呼び出しExecutionContext、したがってSynchronizationContextを維持しているAsyncMethodBuilder /非同期ステートマシンの内部で復元されるのExecutionContextをどうするのです。これのコードcan been seen here(感謝@VMAtm)

関連する問題