'await'가 작동하지만 task.Result 호출이 중단 / 교착 상태


126

다음 네 가지 테스트가 있으며 마지막 테스트는 실행할 때 중단됩니다. 왜 이런 일이 발생합니까?

[Test]
public void CheckOnceResultTest()
{
    Assert.IsTrue(CheckStatus().Result);
}

[Test]
public async void CheckOnceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceAwaitTest()
{
    Assert.IsTrue(await CheckStatus());
    Assert.IsTrue(await CheckStatus());
}

[Test]
public async void CheckStatusTwiceResultTest()
{
    Assert.IsTrue(CheckStatus().Result); // This hangs
    Assert.IsTrue(await CheckStatus());
}

private async Task<bool> CheckStatus()
{
    var restClient = new RestClient(@"https://api.test.nordnet.se/next/1");
    Task<IRestResponse<DummyServiceStatus>> restResponse = restClient.ExecuteTaskAsync<DummyServiceStatus>(new RestRequest(Method.GET));
    IRestResponse<DummyServiceStatus> response = await restResponse;
    return response.Data.SystemRunning;
}

restsharp RestClient 에 대해이 확장 방법을 사용합니다 .

public static class RestClientExt
{
    public static Task<IRestResponse<T>> ExecuteTaskAsync<T>(this RestClient client, IRestRequest request) where T : new()
    {
        var tcs = new TaskCompletionSource<IRestResponse<T>>();
        RestRequestAsyncHandle asyncHandle = client.ExecuteAsync<T>(request, tcs.SetResult);
        return tcs.Task;
    }
}
public class DummyServiceStatus
{
    public string Message { get; set; }
    public bool ValidVersion { get; set; }
    public bool SystemRunning { get; set; }
    public bool SkipPhrase { get; set; }
    public long Timestamp { get; set; }
}

마지막 테스트가 중단되는 이유는 무엇입니까?


7
비동기 메서드에서 void를 반환하지 않아야합니다. 대부분 인터페이스 코드에서 기존 이벤트 핸들러와의 하위 호환성을 위해서만 사용됩니다. 비동기 메서드가 아무것도 반환하지 않으면 Task를 반환해야합니다. MSTest에 많은 문제가 있었고 비동기 테스트를 반환하지 않았습니다.
— ghord

2
@ghord : MSTest는 async void단위 테스트 방법을 전혀 지원하지 않습니다 . 그들은 단순히 작동하지 않을 것입니다. 그러나 NUnit은 그렇습니다. 그게 내가 선호의 일반 원칙에 동의했다 async Task이상 async void.
— Stephen Cleary

@StephenCleary 예, VS2012의 베타 버전에서 허용되었지만 모든 종류의 문제가 발생했습니다.
— ghord

답변:


88

내 블로그 와 MSDN 기사에서 설명하는 표준 교착 상태 상황에 처해 있습니다 .이 async메서드는 .NET에 대한 호출에 의해 차단되는 스레드에 대한 연속 작업을 예약하려고합니다 Result.

이 경우 SynchronizationContextNUnit에서 async void테스트 메서드 를 실행하는 데 사용하는 것은 your 입니다 . async Task대신 테스트 방법을 사용해 보겠습니다 .


4
비동기 작업으로 변경하면 이제 링크의 내용을 몇 번 읽어야합니다.
— Johan Larsson

@MarioLopez : 해결책은 " async모든 방법" 을 사용 하는 것입니다 (MSDN 문서에 언급 됨). 즉, 내 블로그 게시물의 제목에서 "비동기 코드를 차단하지 마십시오"라고 적혀 있습니다.
— Stephen Cleary

1
@StephenCleary 생성자 내부에서 비동기 메서드를 호출해야하는 경우 어떻게해야합니까? 생성자는 비동기 일 수 없습니다.
— Raikol Amaro 2018 년

1
@StephenCleary SO와 기사에 대한 거의 모든 답변에서 내가 본 모든 Wait()것은 호출 메서드를 만드는 것으로 대체 하는 것 async입니다. 그러나 나에게는 이것이 문제를 상류로 밀어 붙이는 것 같습니다. 어떤 시점에서 무언가 를 동시에 관리해야합니다. 내 함수가 장기 실행 작업자 스레드를 관리하기 때문에 의도적으로 동기식이면 어떻게 Task.Run()됩니까? NUnit 테스트 내에서 교착 상태없이 완료 될 때까지 어떻게 기다리나요?
— void.pointer

1
@ void.pointer : At some point, something has to be managed synchronously.-전혀 아닙니다. UI 앱의 경우 진입 점이 async void이벤트 핸들러 가 될 수 있습니다 . 서버 앱의 경우 진입 점이 async Task<T>작업 이 될 수 있습니다 . async스레드 차단을 방지하려면 둘 다 사용 하는 것이 좋습니다 . NUnit 테스트를 동기 또는 비동기로 설정할 수 있습니다. 비동기 경우, 그것을 만드는 async Task대신 async void. 동기식이라면 SynchronizationContext교착 상태가 없어야합니다.
— Stephen Cleary

222

비동기 메서드를 통해 값 획득 :

var result = Task.Run(() => asyncGetValue()).Result;

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

Task.Run( () => asyncMethod()).Wait();

Task.Run의 사용으로 인해 교착 상태 문제가 발생하지 않습니다.


15
-1은 async void단위 테스트 방법 의 사용을 장려하고 테스트 SynchronizationContext중인 시스템에서에서 제공하는 동일한 스레드 보장을 제거 합니다.
— Stephen Cleary 2015 년

68
@StephenCleary : 비동기 무효의 "장려"가 없습니다. 교착 상태 문제를 해결하기 위해 유효한 C # 구조를 사용하는 것입니다. 위의 스 니펫은 OP의 문제에 대한 필수적이고 간단한 해결 방법입니다. Stackoverflow는 장황한 자기 홍보가 아니라 문제에 대한 해결책에 관한 것입니다.
— Herman Schoenfeld 2015 년

81
@StephenCleary : 귀하의 기사는 솔루션을 실제로 명확하게 표현하지 않으며 (적어도 명확하게) 솔루션이 있더라도 이러한 구성을 간접적으로 사용합니다. 내 솔루션은 컨텍스트를 명시 적으로 사용하지 않습니다. 그렇다면 어떻게해야합니까? 요점은 내 작품이 한 줄짜리라는 것입니다. 문제를 해결하기 위해 두 개의 블로그 게시물과 수천 개의 단어가 필요하지 않았습니다. 참고 : 나는 심지어 async void를 사용하지 않기 때문에 당신이 무엇을하고 있는지 정말 모릅니다 .. 내 간결하고 적절한 대답에 "async void"가 보이나요?
— Herman Schoenfeld

15
@HermanSchoenfeld, 방법에 이유 를 추가하면 귀하의 답변이 많은 이점을 얻을 것이라고 믿습니다.
— ironstone13

19
나는 이것이 다소 늦다는 것을 알고 있지만 .GetAwaiter().GetResult()대신에 사용하여 포장되지 .Result않도록해야합니다 Exception.
— Camilo Terevinto

15

ConfigureAwait(false)이 줄에 교착 상태가 추가 되는 것을 방지 할 수 있습니다 .

IRestResponse<DummyServiceStatus> response = await restResponse;

=>

IRestResponse<DummyServiceStatus> response = await restResponse.ConfigureAwait(false);

내 블로그 게시물 Pitfalls of async / await 에서이 함정을 설명했습니다.


9

Task.Result 속성을 사용하여 UI를 차단하고 있습니다. 에서 MSDN 문서 그들은 분명히 그것을 언급

" 결과 속성은 차단 속성입니다. 작업이 완료되기 전에 액세스하려고하면 작업이 완료되고 값을 사용할 수있을 때까지 현재 활성화 된 스레드가 차단됩니다. 대부분의 경우 Await 를 사용하여 값에 액세스해야합니다. 또는 속성에 직접 액세스하는 대신 기다 립니다. "

이 시나리오에 대한 최상의 솔루션 은 메서드에서 await 및 async 를 모두 제거하고 결과를 반환하는 작업 만 사용 하는 것입니다. 실행 순서를 엉망으로 만들지 않습니다.


3

콜백을받지 못하거나 컨트롤이 중단되면 서비스 / API 비동기 함수를 호출 한 후 동일한 호출 된 컨텍스트에서 결과를 반환하도록 컨텍스트를 구성해야합니다.

사용하다 TestAsync().ConfigureAwait(continueOnCapturedContext: false);

이 문제는 웹 응용 프로그램에서만 발생하고 static void main.


ConfigureAwait원래 스레드 컨텍스트에서 실행되지 않음으로써 특정 시나리오에서 교착 상태를 방지합니다.
— davidcarr
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.