.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;
}
그래서 비동기 메서드를 동기식으로 호출하는 것이 가장 좋습니다.
첫째, 이것은 할 수있는 좋은 일입니다. 스택 오버 플로우 (Stack Overflow)는 구체적인 경우에 관계없이 담요 선언문으로 악마의 행위로 이것을 지적하기 때문에이 말을하고 있습니다.
모든 방법으로 비동기적일 필요는 없습니다.정확함을 위해. 동기화를 위해 비동기적인 것을 차단하면 성능 비용이 중요하거나 전혀 상관이 없을 수 있습니다. 구체적인 경우에 따라 다릅니다.
교착 상태는 동일한 단일 스레드 동기화 컨텍스트를 동시에 입력하려고하는 두 스레드에서 발생합니다. 이를 피하는 모든 기술은 블로킹으로 인해 발생하는 교착 상태를 확실하게 방지합니다.
여기에 대한 모든 전화는.ConfigureAwait(false)
당신이 기다리고 있지 않기 때문에 무의미합니다.
RunSynchronously
모든 작업을 그렇게 처리 할 수있는 것은 아니기 때문에 사용할 수 없습니다.
.GetAwaiter().GetResult()
~과 다르다.Result/Wait()
그것이await
예외 전파 행위. 당신은 당신이 원하는 것인지 아닌지 결정해야합니다. (그래서 그 행동이 무엇인지 연구해라, 그것을 여기에서 반복 할 필요가 없다.)
그 외에도 이러한 모든 접근 방식은 비슷한 성능을 제공합니다. 그들은 OS 이벤트를 다른 방식으로 할당하고 차단합니다. 그것은 비싼 부분입니다. 어떤 접근 방식이 절대적으로 가장 저렴한지 모르겠습니다.
나는 개인적으로Task.Run(() => DoSomethingAsync()).Wait();
교착 상태를 명확하게 피하기 때문에 패턴은 간단하고 일부 예외를 숨기지 않습니다.GetResult()
숨길 수 있습니다. 하지만 당신은GetResult()
뿐만 아니라.
.NET 3.5에서 작성된 API 표면이있는 라이브러리를 업데이트하는 중입니다. 결과적으로 모든 메소드는 동기식입니다. 모든 발신자가 변경되어야하므로 API를 변경할 수 없습니다 (즉, 반환 값을 작업으로 변환). 그래서 비동기 메서드를 동기식으로 호출하는 것이 가장 좋습니다.
sync-over-async 방지 패턴을 수행하는 보편적 "최상의"방법은 없습니다. 각자의 단점을 가지고있는 다양한 해킹 만 가능합니다.
내가 권장하는 것은 이전의 동기식 API를 유지하고 그 다음에 비동기식 API를 도입하는 것입니다. 이 작업은Brownfield Async의 MSDN 기사에서 설명한 "부울 인수 해킹".
먼저, 예제에서 각 접근법의 문제점에 대한 간략한 설명 :
ConfigureAwait
있을 때만 의미가 있습니다.await
; 그렇지 않으면 아무것도하지 않습니다.Result
예외를AggregateException
; 차단해야하는 경우GetAwaiter().GetResult()
대신.Task.Run
스레드 풀 스레드에서 코드를 실행합니다 (분명히). 이건 괜찮아오직코드양철통스레드 풀 스레드에서 실행됩니다.RunSynchronously
동적 태스크 기반 병렬 처리를 수행 할 때 매우 드문 경우에 사용되는 고급 API입니다. 당신은 전혀 그 시나리오가 아닙니다.Task.WaitAll
하나의 작업으로Wait()
.async () => await x
그냥 덜 효율적인 방법이다.() => x
.여기에 고장이 있습니다.
// 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를 사용하지 않는 것이 좋습니다.
await
반환하기 때문에 메서드의 결과.Task
, 그 이유는async
. 이것은사소한 메소드의 경우, 키워드를 삭제할 수 있습니다. - Stephen ClearyWebClient
. 그러나 코어 메소드가 사용할 때 어떤 이점이 있습니까?HttpClient
? 동기식 메서드가 직접 호출하는 경우에는 더 깨끗하고 효율적입니다.GetCoreSync
메서드, 부울 인수를 해킹이 사건에서 떠나는? - Creepin