2017-04-20 2 views
3

다음 코드의 동작에 대해 이해할 수없는 몇 가지 사항 (그러나 1 가지 주요 사항)이 있습니다.await/async 동기화 컨텍스트 전환 동작을 설명하는 방법

누군가 설명해 드릴 수 있습니까?

사실 아주 간단한 코드입니다. 비동기 메서드를 호출하는 하나의 일반 메서드입니다. 비동기 메서드에서는 using 블록을 사용하여 SynchronizationContext를 일시적으로 변경하려고합니다.

코드의 다른 지점에서 현재 SynchronizationContext를 조사합니다. 여기

내 질문은 다음과 같습니다

실행이 "2.1"문맥 상황 # 2로 변경 한 위치에 도달
  1. . 괜찮아. 그러면 우리는`await`을 만나기 때문에 Task는 이 리턴되고 실행은 1.2로 되돌아갑니다. 그렇다면 위치 1.2에서 컨텍스트가 컨텍스트 # 2에서 "고정"되지 않는 이유는 무엇입니까?

    아마도 여기에 using 문과 async 메서드가있는 마법이 있습니까?
  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 쉽지 않습니다. 당신 await 기본 (new SynchronizationContext) 또는 null에서 SynchronizationContext를 사용하는 경우에는 Post 방법은 연속 위임 전달 호출되는 것 때문에

이유 2.2 인쇄 nullis scheduled on the ThreadPool입니다. 현재 인스턴스를 복원하는 데 아무런 노력을하지 않으며 스레드 풀 (ThreadPool)에서 실행될 때 이러한 연속에 대해 null 인 현재 SynchronizationContext에 의존합니다. 확실하게하기 위해 .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).

관련 문제