교착 상태를 일으키는 비동기 / 대기 예제


96

C #의 async/ await키워드를 사용하는 비동기 프로그래밍에 대한 몇 가지 모범 사례를 보았습니다 (C # 5.0을 처음 사용했습니다).

주어진 조언 중 하나는 다음과 같습니다.

안정성 : 동기화 컨텍스트 파악

... 일부 동기화 컨텍스트는 재진입이 불가능하고 단일 스레드입니다. 이는 주어진 시간에 하나의 작업 단위 만 컨텍스트에서 실행될 수 있음을 의미합니다. 이에 대한 예는 Windows UI 스레드 또는 ASP.NET 요청 컨텍스트입니다. 이러한 단일 스레드 동기화 컨텍스트에서 교착 상태가되기 쉽습니다. 단일 스레드 컨텍스트에서 작업을 생성 한 다음 컨텍스트에서 해당 작업을 기다리면 대기 코드가 백그라운드 작업을 차단할 수 있습니다.

public ActionResult ActionAsync()
{
    // DEADLOCK: this blocks on the async task
    var data = GetDataAsync().Result;

    return View(data);
}

private async Task<string> GetDataAsync()
{
    // a very simple async method
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}

내가 직접 해부하려고하면 메인 스레드가에서 새 스레드로 생성 MyWebService.GetDataAsync();되지만 메인 스레드가 거기에서 기다리고 있기 때문에 GetDataAsync().Result. 한편, 데이터가 준비되었다고 가정하십시오. 메인 스레드가 계속 논리를 계속하지 않고 문자열 결과를 반환하는 이유는 GetDataAsync()무엇입니까?

누군가가 위의 예에서 교착 상태가 발생한 이유를 설명해 주시겠습니까? 나는 문제가 무엇인지에 대해 완전히 단서가 없습니다 ...


GetDataAsync가 작업을 완료한다고 정말로 확신합니까? 아니면 교착 상태가 아닌 잠금을 유발하는 문제가 발생합니까?
Andrey

제공된 예입니다. 나의 이해로는 ... 그것의 물건을 완료하고 어떤 종류의 준비의 결과가 있어야
인 Dror 와이즈에게

4
왜 작업을 기다리고 있습니까? 기본적으로 비동기 모델의 모든 이점을 잃었 기 때문에 대신 기다려야합니다.
Toni Petrina 2013

심지어 / O 교착 상태 문제 승, ToniPetrina의 관점 @에 추가 var data = GetDataAsync().Result;해야 코드의 라인입니다 결코 당신이 (UI 또는 ASP.NET 요청)를 차단하지 않아야하는 상황에서 수행되지는. 교착 상태가 아니더라도 불확실한 시간 동안 스레드를 차단합니다. 그래서 기본적으로 끔찍한 예입니다. [그런 코드를 실행하기 전에 UI 스레드에서 벗어나거나 awaitToni가 제안한 것처럼 거기에서도 사용해야 합니다.]
ToolmakerSteve

답변:


82

이 예를 살펴보면 Stephen이 명확한 답변을 제공합니다.

따라서 이것은 최상위 방법 ( Button1_ClickUI MyController.Get용 /ASP.NET 용) 부터 시작됩니다 .

  1. 최상위 메서드 호출 GetJsonAsync(UI / ASP.NET 컨텍스트 내).

  2. GetJsonAsync호출하여 REST 요청을 시작합니다 HttpClient.GetStringAsync(여전히 컨텍스트 내에서).

  3. GetStringAsyncTaskREST 요청이 완료되지 않았 음을 나타내는 uncompleted를 반환합니다 .

  4. GetJsonAsync에서 Task반환 된을 기다립니다 GetStringAsync. 컨텍스트가 캡처되고 GetJsonAsync나중에 메서드 를 계속 실행하는 데 사용됩니다 . 메서드가 완료되지 않았 음을 나타내는 GetJsonAsyncuncompleted를 반환합니다 .TaskGetJsonAsync

  5. 최상위 메서드는에서 Task반환 된를 동 기적으로 차단합니다 GetJsonAsync. 이것은 컨텍스트 스레드를 차단합니다.

  6. ... 결국 REST 요청이 완료됩니다. 이것 Task으로에서 반환 된 이 완료 됩니다 GetStringAsync.

  7. 에 대한 연속 GetJsonAsync은 이제 실행할 준비가되었으며 컨텍스트에서 실행할 수 있도록 컨텍스트를 사용할 수있을 때까지 기다립니다.

  8. 교착 상태 . 최상위 메소드는 컨텍스트 스레드를 차단하고 GetJsonAsync완료 GetJsonAsync를 기다리고 있으며 컨텍스트가 완료 될 수 있도록 비어있을 때까지 기다리고 있습니다. UI 예제에서 "context"는 UI 컨텍스트입니다. ASP.NET 예제의 경우 "context"는 ASP.NET 요청 컨텍스트입니다. 이러한 유형의 교착 상태는 "컨텍스트"에 대해 발생할 수 있습니다.

읽어야 할 또 다른 링크 : Await, UI 및 deadlocks! 어머!


22
  • 사실 1 : GetDataAsync().Result;반환 된 작업이 GetDataAsync()완료되면 실행 되고 그 동안 UI 스레드가 차단됩니다.
  • 사실 2 : await ( return result.ToString()) 의 연속은 실행을 위해 UI 스레드에 대기합니다.
  • 사실 3 :에서 반환 된 작업 GetDataAsync()은 대기중인 연속 작업이 실행될 때 완료됩니다.
  • 사실 4 : UI 스레드가 차단 되었기 때문에 대기중인 연속이 실행되지 않습니다 (사실 1).

이중 자물쇠!

교착 상태는 팩트 1 또는 팩트 2를 피하기 위해 제공된 대안으로 해제 될 수 있습니다.

  • 1,4를 피하십시오. UI 스레드를 차단하는 대신를 var data = await GetDataAsync()사용하면 UI 스레드가 계속 실행됩니다.
  • 2,3을 피하십시오. 차단되지 않은 다른 스레드에 대기의 연속을 큐에 넣습니다. 예를 들어 var data = Task.Run(GetDataAsync).Result스레드 풀 스레드의 동기화 컨텍스트에 연속을 게시하는 use . 이를 통해에서 반환 된 작업 GetDataAsync()을 완료 할 수 있습니다 .

이것은 Stephen Toub기사 에서 매우 잘 설명 되어 DelayAsync()있습니다.


에 관해서는 var data = Task.Run(GetDataAsync).Result, 그것은 나에게 새로운 일입니다. 나는 항상 .Result첫 번째 기다림 GetDataAsync이 히트 하자마자 아우터 를 쉽게 구할 수 있다고 생각 했으므로 data항상 default. 흥미 롭군.
nawfal

19

ASP.NET MVC 프로젝트에서이 문제를 다시 다루고있었습니다. async에서 메서드 를 호출하려는 PartialView경우 PartialView async. 그렇게하면 예외가 발생합니다.

async동기화 메서드에서 메서드 를 호출하려는 시나리오에서 다음과 같은 간단한 해결 방법을 사용할 수 있습니다 .

  1. 통화하기 전에 SynchronizationContext
  2. 전화 해, 여기에 더 이상 교착 상태가 없을 것입니다. 완료 될 때까지 기다리십시오.
  3. 복원 SynchronizationContext

예:

public ActionResult DisplayUserInfo(string userName)
{
    // trick to prevent deadlocks of calling async method 
    // and waiting for on a sync UI thread.
    var syncContext = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext(null);

    //  this is the async call, wait for the result (!)
    var model = _asyncService.GetUserInfo(Username).Result;

    // restore the context
    SynchronizationContext.SetSynchronizationContext(syncContext);

    return PartialView("_UserInfo", model);
}

3

또 다른 요점은 작업을 차단해서는 안되며 교착 상태를 방지하기 위해 비동기식을 사용하는 것입니다. 그러면 모두 비동기식이 아닌 비동기식 차단이됩니다.

public async Task<ActionResult> ActionAsync()
{

    var data = await GetDataAsync();

    return View(data);
}

private async Task<string> GetDataAsync()
{
    // a very simple async method
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}

6
내가하면 어떻게 주 (UI) 스레드가 작업이 완료 될 때까지 차단 될? 또는 예를 들어 콘솔 앱에서? 비동기 지원 하는 HttpClient를 사용하고 싶다고 가정 해 보겠습니다. 교착 상태의 위험없이 어떻게 동 기적으로 사용 합니까? 이것은 가능 해야 합니다. WebClient를 그런 방식으로 사용할 수 있고 (동기화 메서드가 있기 때문에) 완벽하게 작동한다면 왜 HttpClient로도 수행 할 수 없습니까?
Dexter

위의 Philip Ngan의 답변을 참조하십시오 (이 댓글 뒤에 게시 된 것을 알고 있습니다) : 차단되지 않은 다른 스레드에 대기의 연속을 큐에 넣습니다. 예를 들어 var data = Task.Run (GetDataAsync)를 사용합니다 .Result
Jeroen

@Dexter-re " 작업이 완료 될 때까지 기본 (UI) 스레드를 차단하려면 어떻게해야 합니까? "-사용자가 아무것도 할 수없고 취소도 할 수 없음을 의미하는 UI 스레드를 차단 하시겠습니까? 당신이있는 방법을 계속하고 싶지 않다는 것입니까? "await"또는 "Task.ContinueWith"는 후자의 경우를 처리합니다.
ToolmakerSteve

@ToolmakerSteve 물론 방법을 계속하고 싶지 않습니다. 그러나 await를 사용할 수는 없습니다. 왜냐하면 async를 사용할 수 없기 때문입니다. HttpClient는 main 에서 호출되며 물론 비동기 일 수 없습니다. 그런 다음 콘솔 앱에서이 모든 작업을 수행한다고 언급했습니다.이 경우에는 정확히 전자를 원합니다. 내 앱 다중 스레드가 되는 것도 원하지 않습니다 . 모든 것을 차단하십시오 .
Dexter

-1

내가 찾은 해결 Join방법은 결과를 요청하기 전에 작업에 확장 방법 을 사용하는 것입니다.

코드는 다음과 같습니다.

public ActionResult ActionAsync()
{
  var task = GetDataAsync();
  task.Join();
  var data = task.Result;

  return View(data);
}

결합 방법은 다음과 같습니다.

public static class TaskExtensions
{
    public static void Join(this Task task)
    {
        var currentDispatcher = Dispatcher.CurrentDispatcher;
        while (!task.IsCompleted)
        {
            // Make the dispatcher allow this thread to work on other things
            currentDispatcher.Invoke(delegate { }, DispatcherPriority.SystemIdle);
        }
    }
}

이 솔루션의 단점을 파악하기에 도메인에 충분하지 않습니다 (있는 경우).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.