code

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

codestyles 2020. 12. 1. 08:03
반응형

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


.NET 3.5에서 빌드 된 API 표면이있는 라이브러리를 업데이트하는 중입니다. 결과적으로 모든 메서드는 동기식입니다. 모든 호출자가 변경되어야하기 때문에 API를 변경할 수 없습니다 (즉, 반환 값을 Task로 변환). 그래서 동기 방식으로 비동기 메서드를 가장 잘 호출하는 방법이 남았습니다. 이것은 ASP.NET 4, ASP.NET Core 및 .NET / .NET Core 콘솔 애플리케이션의 컨텍스트에 있습니다.

충분히 명확하지 않을 수 있습니다. 상황은 비동기 인식이없는 기존 코드가 있고 비동기 메서드 만 지원하는 System.Net.Http 및 AWS SDK와 같은 새로운 라이브러리를 사용하고 싶습니다. 따라서 격차를 해소하고 동 기적으로 호출 할 수 있지만 다른 곳에서 비동기 메서드를 호출 할 수있는 코드를 가질 수 있어야합니다.

나는 많은 독서를했고, 이것에 대한 질문과 답변을 많이 받았습니다.

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

비동기 작업을 동기식으로 기다리고 있으며 Wait ()가 여기에서 프로그램을 정지시키는 이유

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

비동기 Task <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;
    }

그래서 동기식으로 비동기 메서드를 가장 잘 호출하는 방법이 남았습니다.

첫째, 이것은 괜찮은 일입니다. 이것은 Stack Overflow에서이 사실을 구체적인 사례에 관계없이 총괄적인 진술로 악마의 행동으로 지적하는 것이 일반적이기 때문에 이것을 언급하고 있습니다.

정확성을 위해 비동기식 일 필요는 없습니다 . 무언가를 동기화하기 위해 비동기로 차단하는 것은 중요하거나 전혀 관련이 없을 수있는 성능 비용이 있습니다. 구체적인 경우에 따라 다릅니다.

교착 상태는 동일한 단일 스레드 동기화 컨텍스트를 동시에 입력하려고하는 두 스레드에서 발생합니다. 이를 방지하는 모든 기술은 차단으로 인한 교착 상태를 안정적으로 방지합니다.

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

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

.GetAwaiter().GetResult()예외 전파 동작을 Result/Wait()모방한다는 점에서 다릅니다 await. 원하는지 여부를 결정해야합니다. (그러니 그 행동이 무엇인지 조사하십시오. 여기서 반복 할 필요가 없습니다.)

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

나는 개인적으로 Task.Run(() => DoSomethingAsync()).Wait();패턴이 교착 상태를 피하고 단순하며 숨길 수있는 일부 예외를 숨기지 않기 때문에 좋아합니다 GetResult(). 그러나 GetResult()이것으로도 사용할 수 있습니다 .


.NET 3.5에서 빌드 된 API 표면이있는 라이브러리를 업데이트하는 중입니다. 결과적으로 모든 메서드는 동기식입니다. 모든 호출자가 변경되어야하기 때문에 API를 변경할 수 없습니다 (즉, 반환 값을 Task로 변환). 그래서 동기 방식으로 비동기 메서드를 가장 잘 호출하는 방법이 남았습니다.

비동기식 동기화 방지 패턴을 수행하는 보편적 인 "최상의"방법은 없습니다. 각각의 단점이있는 다양한 해킹 만 있습니다.

내가 권장하는 것은 이전 동기 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);

With this approach, your logic would go into the Core methods, which may be run synchronously or asynchronously (as determined by the sync parameter). If sync is true, then the core methods must return an already-completed task. For implemenation, use synchronous APIs to run synchronously, and use asynchronous APIs to run asynchronously.

Eventually, I recommend deprecating the synchronous APIs.

참고URL : https://stackoverflow.com/questions/40324300/calling-async-methods-from-non-async-code

반응형