'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.