43

.NET 3.5에서 작성된 API 표면이있는 라이브러리를 업데이트하는 중입니다. 결과적으로 모든 메소드는 동기식입니다. 모든 발신자가 변경되어야하므로 API를 변경할 수 없습니다 (즉, 반환 값을 작업으로 변환). 그래서 비동기 메서드를 동기식으로 호출하는 것이 가장 좋습니다. 이것은 ASP.NET 4, ASP.NET 코어 및 .NET / .NET 코어 콘솔 응용 프로그램의 컨텍스트에 있습니다.

내가 충분히 명확하지 않을 수도 있습니다 - 상황은 내가 비동기 인식하지 않는 기존 코드를 가지고 있고 System.Net.Http와 비동기 메소드만을 지원하는 AWS SDK와 같은 새로운 라이브러리를 사용하고자합니다. 그래서 나는 갭을 메워야하고 동 기적으로 호출 될 수있는 코드를 가질 수 있어야하지만 다른 곳에서는 비동기 메소드를 호출 할 수 있어야합니다.

나는 많은 책을 읽었으며, 여러 번 묻고 답했습니다.

비동기 메서드에서 비동기 메서드 호출

동기로 비동기 작업을 기다리는 중입니다. 왜 Wait ()이 프로그램을 멈추게합니까?

동기 메서드에서 비동기 메서드 호출

비동기 작업 < T >을 어떻게 실행합니까? 메서드를 동 기적으로?

비동기 메서드를 동 기적으로 호출

C #에서 동기 메서드에서 비동기 메서드를 호출하는 방법?

문제는 대부분의 답변이 다르다는 것입니다. 필자가 보아온 가장 보편적 인 접근 방법은 .Result를 사용하는 것이지만 교착 상태에 빠질 수 있습니다. 나는 다음을 모두 시도해 보았지만 작동하지만 교착 상태를 피하고 성능이 뛰어나고 (작업 스케줄러, 작업 생성 옵션 등을 존중하는 관점에서) 런타임과 잘 작동하는 것이 가장 좋은 방법인지 잘 모르겠습니다. ). 확실한 답이 있습니까? 가장 좋은 방법은 무엇입니까?

private static T taskSyncRunner<T>(Func<Task<T>> task)
    {
        T result;
        // approach 1
        result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 2
        result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 3
        result = task().ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 4
        result = Task.Run(task).Result;

        // approach 5
        result = Task.Run(task).GetAwaiter().GetResult();


        // approach 6
        var t = task();
        t.RunSynchronously();
        result = t.Result;

        // approach 7
        var t1 = task();
        Task.WaitAll(t1);
        result = t1.Result;

        // approach 8?

        return result;
    }


  • 대답은 당신이하지 않는다는 것입니다. 비동기적인 새로운 메소드를 추가하고 기존의 호출자에 대해 이전의 동기 메소드를 유지합니다. - Scott Chamberlain
  • 이는 약간 가혹한 것으로 보이며 실제로 새로운 코드를 사용할 수있는 능력을 없애줍니다. 예를 들어, 새 버전의 AWS SDK에는 비동기 메소드가 없습니다. 다른 여러 제 3 자 라이브러리와 동일합니다. 그래서 당신이 세상을 다시 쓰지 않으면, 이것들을 사용할 수 없습니까? - Erick T
  • 옵션 8 : 아마도TaskCompletionSource옵션 일 수 있습니까? - OrdinaryOrange
  • 작업이 실행 중일 때 Result 속성은 교착 상태를 발생시킵니다. 100 % 확신 할 수는 없지만 방법 7과 같이 작업이 끝날 때까지 제대로 기다리면 안전해야합니다. - infiniteRefactor
  • 비동기 코드에서 동기 API를 사용할 수없는 이유의 샘플을 표시하면 @scott에 의한 것보다 더 나은 답변을 찾는 데 도움이 될 수 있습니다. - Alexei Levenkov

2 답변


58

그래서 비동기 메서드를 동기식으로 호출하는 것이 가장 좋습니다.

첫째, 이것은 할 수있는 좋은 일입니다. 스택 오버 플로우 (Stack Overflow)는 구체적인 경우에 관계없이 담요 선언문으로 악마의 행위로 이것을 지적하기 때문에이 말을하고 있습니다.

모든 방법으로 비동기적일 필요는 없습니다.정확함을 위해. 동기화를 위해 비동기적인 것을 차단하면 성능 비용이 중요하거나 전혀 상관이 없을 수 있습니다. 구체적인 경우에 따라 다릅니다.

교착 상태는 동일한 단일 스레드 동기화 컨텍스트를 동시에 입력하려고하는 두 스레드에서 발생합니다. 이를 피하는 모든 기술은 블로킹으로 인해 발생하는 교착 상태를 확실하게 방지합니다.

여기에 대한 모든 전화는.ConfigureAwait(false)당신이 기다리고 있지 않기 때문에 무의미합니다.

RunSynchronously모든 작업을 그렇게 처리 할 수있는 것은 아니기 때문에 사용할 수 없습니다.

.GetAwaiter().GetResult()~과 다르다.Result/Wait()그것이await예외 전파 행위. 당신은 당신이 원하는 것인지 아닌지 결정해야합니다. (그래서 그 행동이 무엇인지 연구해라, 그것을 여기에서 반복 할 필요가 없다.)

그 외에도 이러한 모든 접근 방식은 비슷한 성능을 제공합니다. 그들은 OS 이벤트를 다른 방식으로 할당하고 차단합니다. 그것은 비싼 부분입니다. 어떤 접근 방식이 절대적으로 가장 저렴한지 모르겠습니다.

나는 개인적으로Task.Run(() => DoSomethingAsync()).Wait();교착 상태를 명확하게 피하기 때문에 패턴은 간단하고 일부 예외를 숨기지 않습니다.GetResult()숨길 수 있습니다. 하지만 당신은GetResult()뿐만 아니라.



20

.NET 3.5에서 작성된 API 표면이있는 라이브러리를 업데이트하는 중입니다. 결과적으로 모든 메소드는 동기식입니다. 모든 발신자가 변경되어야하므로 API를 변경할 수 없습니다 (즉, 반환 값을 작업으로 변환). 그래서 비동기 메서드를 동기식으로 호출하는 것이 가장 좋습니다.

sync-over-async 방지 패턴을 수행하는 보편적 "최상의"방법은 없습니다. 각자의 단점을 가지고있는 다양한 해킹 만 가능합니다.

내가 권장하는 것은 이전의 동기식 API를 유지하고 그 다음에 비동기식 API를 도입하는 것입니다. 이 작업은Brownfield Async의 MSDN 기사에서 설명한 "부울 인수 해킹".

먼저, 예제에서 각 접근법의 문제점에 대한 간략한 설명 :

  1. ConfigureAwait있을 때만 의미가 있습니다.await; 그렇지 않으면 아무것도하지 않습니다.
  2. Result예외를AggregateException; 차단해야하는 경우GetAwaiter().GetResult()대신.
  3. Task.Run스레드 풀 스레드에서 코드를 실행합니다 (분명히). 이건 괜찮아오직코드양철통스레드 풀 스레드에서 실행됩니다.
  4. RunSynchronously동적 태스크 기반 병렬 처리를 수행 할 때 매우 드문 경우에 사용되는 고급 API입니다. 당신은 전혀 그 시나리오가 아닙니다.
  5. Task.WaitAll하나의 작업으로Wait().
  6. async () => await x그냥 덜 효율적인 방법이다.() => x.
  7. 현재 스레드에서 시작된 작업 차단교착 상태를 일으킬 수있다..

여기에 고장이 있습니다.

// Problems (1), (3), (6)
result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult();

// Problems (1), (3)
result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult();

// Problems (1), (7)
result = task().ConfigureAwait(false).GetAwaiter().GetResult();

// Problems (2), (3)
result = Task.Run(task).Result;

// Problems (3)
result = Task.Run(task).GetAwaiter().GetResult();

// Problems (2), (4)
var t = task();
t.RunSynchronously();
result = t.Result;

// Problems (2), (5)
var t1 = task();
Task.WaitAll(t1);
result = t1.Result;

이러한 접근 방식 중 하나 대신,기존의 작동중인 동기 코드, 당신은 더 자연스럽게 비동기적인 코드와 함께 사용해야합니다. 예를 들어, 기존 코드가 사용 된 경우WebClient:

public string Get()
{
  using (var client = new WebClient())
    return client.DownloadString(...);
}

비동기 API를 추가하려면 다음과 같이하면됩니다.

private async Task<string> GetCoreAsync(bool sync)
{
  using (var client = new WebClient())
  {
    return sync ?
        client.DownloadString(...) :
        await client.DownloadStringTaskAsync(...);
  }
}

public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult();

public Task<string> GetAsync() => GetCoreAsync(sync: false);

또는,절대로 필요한 것용도HttpClient몇 가지 이유:

private string GetCoreSync()
{
  using (var client = new WebClient())
    return client.DownloadString(...);
}

private static HttpClient HttpClient { get; } = ...;

private async Task<string> GetCoreAsync(bool sync)
{
  return sync ?
      GetCoreSync() :
      await HttpClient.GetString(...);
}

public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult();

public Task<string> GetAsync() => GetCoreAsync(sync: false);

이 방법을 사용하면 논리가Core메소드는 동기식 또는 비동기식으로 실행될 수 있습니다 (sync매개 변수). 만약sync~이다.true, 핵심 방법절대로 필요한 것이미 완료된 작업을 반환합니다. 구현을 위해서는 동기 API를 사용하여 동 기적으로 실행하고 비동기 API를 사용하여 비동기 적으로 실행하십시오.

결국 동기 API를 사용하지 않는 것이 좋습니다.


  • 6 번 항목에 대해 더 설명해 주시겠습니까? - Emerson Soares
  • @EmersonSoares : 내가 설명하는대로비동기 소개, 당신은 할 수있다.await반환하기 때문에 메서드의 결과.Task, 그 이유는async. 이것은사소한 메소드의 경우, 키워드를 삭제할 수 있습니다. - Stephen Cleary
  • 나는 asp.net mvc 5 + EF6에서 같은 문제로 최근에 직면했다. 이 답변에서 제안하는대로 TaskFactory를 사용했습니다.stackoverflow.com/a/25097498/1683040, 그리고 나를 위해 속임수 :),하지만 다른 시나리오에 대한 확실하지 않았다. - LeonardoX
  • 귀하의 제안을 이해합니다.WebClient. 그러나 코어 메소드가 사용할 때 어떤 이점이 있습니까?HttpClient? 동기식 메서드가 직접 호출하는 경우에는 더 깨끗하고 효율적입니다.GetCoreSync메서드, 부울 인수를 해킹이 사건에서 떠나는? - Creepin
  • @ 크 레핀 : 네, 그렇게 믿습니다. - Stephen Cleary

연결된 질문


관련된 질문

최근 질문