2014-11-24 5 views
9

저는 현재 async 라이브러리 코드를 많이 작성하고 있으며, 연속 코드를 원래 (대개 UI) 스레드 컨텍스트로 마샬링하는 것을 피하기 위해 각 비동기 호출 후에 ConfigureAwait(false)을 추가하는 관행을 알고 있습니다. 레이블이 지정되지 않은 부울 매개 변수가 마음에 들지 않아 대신 ConfigureAwait(continueOnCapturedContext: false)으로 작성했습니다. 비동기 메서드 내에서 ConfigureAwait (false)를 호출하는 대신 읽을 수있는 대안이 있습니까?

나는 그것을 좀 더 읽기 쉽게 (그리고 다소 입력을 감소)하는 확장 메서드를 추가 :

public static class TaskExtensions 
{ 
    public static ConfiguredTaskAwaitable<TResult> WithoutCapturingContext<TResult>(this Task<TResult> task) 
    { 
     return task.ConfigureAwait(continueOnCapturedContext: false); 
    } 

    public static ConfiguredTaskAwaitable WithoutCapturingContext(this Task task) 
    { 
     return task.ConfigureAwait(continueOnCapturedContext: false); 
    } 
} 

을 그래서 지금은 await SomethingAsync().WithoutCapturingContext() 대신 await SomethingAsync().ConfigureAwait(continueOnCapturedContext: false) 같은 것을 할 수 있습니다.

await FooAsync().WithoutCapturingContext(); 
var bar = await BarAsync().WithoutCapturingContext(); 
await MoreFooAsync().WithoutCapturingContext(); 
var moreBar = await MoreBarAsync().WithoutCapturingContext(); 
// etc, etc 

제 생각에는 그건 : 나는 동일한 코드 블록에 async 메소드를 호출 할 때이 비슷한 끝낼 그러나 심지어이, 창살 시작, 그것을 개선 고려 코드를 훨씬 쉽게 읽을 수있게 만들기 시작했습니다.

제 질문은 기본적으로 다음과 같습니다 (확장 메서드의 이름을 줄이는 것 외에는)이 방법을 줄이는 방법이 있습니까?

+0

는 항상 모든 매개 변수를 레이블 마십시오 : 아래 가능한 IgnoreContext의 구현 (매우 약간 테스트)입니다? – i3arnon

+0

'ConfigureAwait()'를 낮은 레벨로 옮길 수 있습니다. 예. 'BarAsync()'를 래퍼 (wrapper)로 만드십시오.이 래퍼는 기본적으로 BarAsync(), WithoutCapturingContext()를 기다리고 있습니다. –

+0

@ I3arnon 다양하지만 부울 매개 변수를 거의 항상 레이블링합니다. 이 경우 잘 알려진 패턴이지만 매개 변수를 레이블하는 데 좋은 인수가 있습니다. –

답변

7

메서드 내의 작업이 동기화 컨텍스트를 캡처하지 못하도록하는 전역 설정은 없지만 일 수 있습니다. 해당 메서드의 범위에 대한 동기화 컨텍스트 만 변경하면됩니다. 특정 경우에는 해당 방법의 범위에 대한 기본 동기화 컨텍스트로 컨텍스트를 변경할 수 있습니다.

이 동기화 컨텍스트를 변경하는 간단한 일회용 클래스를 작성하기 쉽고 충분하고 배치 할 때 다시 변경 :

public class SyncrhonizationContextChange : IDisposable 
{ 
    private SynchronizationContext previous; 
    public SyncrhonizationContextChange(SynchronizationContext newContext = null) 
    { 
     previous = SynchronizationContext.Current; 
     SynchronizationContext.SetSynchronizationContext(newContext); 
    } 

    public void Dispose() 
    { 
     SynchronizationContext.SetSynchronizationContext(previous); 
    } 
} 

당신이 쓸 수 있도록 :

using(var change = new SyncrhonizationContextChange()) 
{ 
    await FooAsync(); 
    var bar = await BarAsync(); 
    await MoreFooAsync(); 
    var moreBar = await MoreBarAsync(); 
} 

(참고 컨텍스트를 설정 null은 기본 컨텍스트를 사용함을 의미합니다.

+0

나는 이것을 좋아하지 만 약간의 인터넷 검색 후에는 거의 사용되지 않으며 비표준적인 것으로 보인다. 이 경로를 따라 가기위한 부작용이 있습니까? 또한이 경우 "기본 컨텍스트"는 무엇을 의미합니까? –

+0

또한 호출 된 비동기 메소드에 어떤 영향을 미칩니 까? 예를 들어,'FooAsync'에서 비동기 메소드가 호출되면,''null ''SynchronizationContext'를 얻거나'FooAsync' 내에'SynchronizationContextChange'를 복제해야합니까? –

+1

@StevenRands 기본 컨텍스트는'ConfigureAwait (false)'를 사용할 때 사용한 컨텍스트와 정확하게 일치합니다. 당신은 구체적으로 연속이 예정된 시간에 현재 컨텍스트를 "기억"하지 않고 대신 스레드 풀을 사용할 기본 컨텍스트를 사용하도록 지정했습니다. 모든 단일 비동기 메서드가 현재 컨텍스트를 사용하지 않고 단순히 현재 컨텍스트를 사용하려는 컨텍스트로 설정하는 것입니다. 현재 컨텍스트를 설정할 때,이 컨텍스트는 다른 것으로 설정 될 때까지 계속 유지됩니다.이 경우에는 'using'블록을 떠날 때입니다. – Servy

4

짧은 대답은 아니오입니다.

매번 ConfigureAwait을 개별적으로 사용해야하며 글로벌 구성이나 이와 유사한 것이 없습니다. 당신은 말했듯이 짧은 이름으로 확장 메소드를 사용할 수 있지만 그다지 변하지는 않습니다. 아마도을 붙인 코드 (어쩌면 Roslyn을 사용하는 것) 로의 변형을 구현할 수는 있지만 제 의견으로는 신뢰할 수 없습니다. 결국 await 또는 ;과 같이 꼭 필요할 때 작성해야합니다.

4

은 동기화 컨텍스트. 때로는 null이 아닌 동기화 컨텍스트가있는 비 풀 스레드에서 실제 연속이 트리거되었지만 push the await continuation to a pool thread 일 수 있습니다. IMO, ConfigureAwait(false)의 이러한 동작은 놀라운 일이며 비 직관적 일 수 있습니다. 적어도 부작용은 여분의 스레드 전환 일 것입니다.

await MoreFooAsync().IgnoreContext(); 

: 당신이 정말로 스레드/문맥이 될 일이 무엇에 await 후 계속 스레드의 동기화 컨텍스트를 무시하고 단지 동기 (TaskContinuationOptions.ExecuteSynchronously를) 실행을 재개하려면

, 사용자 정의 awaiter을 사용할 수 있습니다

public static class TaskExt 
{ 
    // Generic Task<TResult> 

    public static IgnoreContextAwaiter<TResult> IgnoreContext<TResult>(this Task<TResult> @task) 
    { 
     return new IgnoreContextAwaiter<TResult>(@task); 
    } 

    public struct IgnoreContextAwaiter<TResult> : 
     System.Runtime.CompilerServices.ICriticalNotifyCompletion 
    { 
     Task<TResult> _task; 

     public IgnoreContextAwaiter(Task<TResult> task) 
     { 
      _task = task; 
     } 

     // custom Awaiter methods 
     public IgnoreContextAwaiter<TResult> GetAwaiter() 
     { 
      return this; 
     } 

     public bool IsCompleted 
     { 
      get { return _task.IsCompleted; } 
     } 

     public TResult GetResult() 
     { 
      // result and exceptions 
      return _task.GetAwaiter().GetResult(); 
     } 

     // INotifyCompletion 
     public void OnCompleted(Action continuation) 
     { 
      // not always synchronous, http://blogs.msdn.com/b/pfxteam/archive/2012/02/07/10265067.aspx 
      _task.ContinueWith(_ => continuation(), TaskContinuationOptions.ExecuteSynchronously); 
     } 

     // ICriticalNotifyCompletion 
     public void UnsafeOnCompleted(Action continuation) 
     { 
      // why SuppressFlow? http://blogs.msdn.com/b/pfxteam/archive/2012/02/29/10274035.aspx 
      using (ExecutionContext.SuppressFlow()) 
      { 
       OnCompleted(continuation); 
      } 
     } 
    } 

    // Non-generic Task 

    public static IgnoreContextAwaiter IgnoreContext(this Task @task) 
    { 
     return new IgnoreContextAwaiter(@task); 
    } 

    public struct IgnoreContextAwaiter : 
     System.Runtime.CompilerServices.ICriticalNotifyCompletion 
    { 
     Task _task; 

     public IgnoreContextAwaiter(Task task) 
     { 
      _task = task; 
     } 

     // custom Awaiter methods 
     public IgnoreContextAwaiter GetAwaiter() 
     { 
      return this; 
     } 

     public bool IsCompleted 
     { 
      get { return _task.IsCompleted; } 
     } 

     public void GetResult() 
     { 
      // result and exceptions 
      _task.GetAwaiter().GetResult(); 
     } 

     // INotifyCompletion 
     public void OnCompleted(Action continuation) 
     { 
      // not always synchronous, http://blogs.msdn.com/b/pfxteam/archive/2012/02/07/10265067.aspx 
      _task.ContinueWith(_ => continuation(), TaskContinuationOptions.ExecuteSynchronously); 
     } 

     // ICriticalNotifyCompletion 
     public void UnsafeOnCompleted(Action continuation) 
     { 
      // why SuppressFlow? http://blogs.msdn.com/b/pfxteam/archive/2012/02/29/10274035.aspx 
      using (ExecutionContext.SuppressFlow()) 
      { 
       OnCompleted(continuation); 
      } 
     } 
    } 
} 
+0

비동기 메서드가 UI 스레드에서 완료되고 UI 스레드에서 계속 실행되지 않는 것이 중요하다면 연속적으로 연속적으로 실행하지 않는 것이 중요합니다. – Servy

+0

@Servy, 요구 사항이 있다면, 'ContinueWith' 람다 내부에서 이것을 설명하기 위해이 코드를 조정하는 것이 아주 쉽습니다. 그러나, 나는 이러한 요구 사항에 대한 실제 시나리오를 제시하려고 노력하고 있으며 지금까지는 할 수 없습니다. 예 : TCS로 래핑 된 WinForm의 타이머 또는 UI 요소 이벤트? 여전히 UI 스레드에서 동 기적으로 처리하고 싶습니다. 대신 UI 스레드가 아닌 스레드로 계속 푸시해야합니다. – Noseratio