ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 데드락 걸리는 async 코드
    @ 17. 1 ~ 18/C# 멀티스레드 2017. 8. 19. 10:14

    내용에 대한 출처

    http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html


    UI Example

    아래 예제를 고려하십시오. 버튼을 클릭하면 REST 호출이 시작되고 텍스트 상자에 결과가 표시됩니다 (이 샘플은 Windows Forms 용이지만 모든 UI 응용 프로그램에도 동일한 원칙이 적용됩니다).

    // My "library" method.
    public static async Task<JObject> GetJsonAsync(Uri uri)
    {
      using (var client = new HttpClient())
      {
        var jsonString = await client.GetStringAsync(uri);
        return JObject.Parse(jsonString);
      }
    }
    
    // My "top-level" method.
    public void Button1_Click(...)
    {
      var jsonTask = GetJsonAsync(...);
      textBox1.Text = jsonTask.Result;
    }


    "GetJson"도우미 메서드는 실제 REST 호출을 만들고 JSON으로 구문 분석합니다. 버튼 클릭 핸들러는 도우미 메서드가 완료 될 때까지 기다린 다음 결과를 표시합니다. 이 코드는 교착 상태가됩니다.


    ASP.NET Example

    이 예제는 매우 유사합니다. 우리는 REST 호출을 수행하는 라이브러리 메소드를 가지고 있습니다.이 메소드는 ASP.NET 컨텍스트 (이 경우 웹 API이지만이 원칙은 모든 ASP.NET 애플리케이션에 적용됩니다)에서만 사용됩니다.

    // My "library" method.
    public static async Task<JObject> GetJsonAsync(Uri uri)
    {
      using (var client = new HttpClient())
      {
        var jsonString = await client.GetStringAsync(uri);
        return JObject.Parse(jsonString);
      }
    }
    
    // My "top-level" method.
    public class MyController : ApiController
    {
      public string Get()
      {
        var jsonTask = GetJsonAsync(...);
        return jsonTask.Result.ToString();
      }
    }

    같은 이유로 이 코드 또한 데드락이 된다.


    데드락의 원인은 무엇인가?

    Task와 await는 기다린 후에 메서드가 계속될때 컨텍스트에서 계속된다는 것을 기억하자

    첫번쨰 경우에 이 컨텍스트는 UI컨텍스트이다.(콘솔응용 프로그램을 제외한 어느 UI에도 적용됨)

    두번째 경우의 컨텍스트는 ASP.NET에 요청한 컨텍스트이다.

    또 다른 중요한 점은 ASP.NET 요청 컨텍스트가 특정 스레드(UI컨텍스트와는 다르게)에 묶여 있지 않지만 한번에 하나의 스레드만 허용한다는 것. (결국 UI 컨텍스트와 같다는..)


    그래서 이것은 최상위 메소드에서부터 시작된다.

    1. 최상위 메서드는 GetJsonAsync를 호출한다.

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

    3. GetStringAsync는 REST 요청이 완료되지 않았음을 나타내는 완료되지 않은 Task를 반환한다.

    4. GetJsonAsync는 GetStringsAsnyc에서 반환하는 Task를 기다리고 있다. 컨텍스트가 캡처되고 나중에 GetJsonAsync 메서드를 계속 실행하는데 사용된다. GetJsonAsync메서드가 완료되지 않았음을 나타내는 Task를 반환한다.

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

    6. 결국 REST의 요청이 완료된다. GetStringAsync에서 반환된 작업을 완료한다.

    7. GetJsonAsync에 대한 계속 실행 준비가 완료되었으며 컨텍스트에서 실행될 수 있도록 컨텍스트가 사용 가능할때까지 기다린다.

    8. 데드락 발생. 최상위 메서드는 컨텍스트 스레드를 차단하고 GetJsonAsync가 완료될떄까지 기다리고 있는 상황에서 GetJsonAsync는 컨텍스트가 완료 될 떄까지 기다리고 있는 중이다.

    서로 완료되기를 기다리고 있는 상황..


    UI예제의 경우 컨텍스트는 UI컨텍스트이고 ASP.NET 예제의 경우 컨텍스트는 ASP.NET요청 컨텍스트 이다. 


    데드락 예방법은?

    1. 라이브러리 비동기 메소드에서 가능하면 ConfigureAwait(false)를 사용한다.

    public static async Task<JObject> GetJsonAsync(Uri uri)
    {
      using (var client = new HttpClient())
      {
        var jsonString = await client.GetStringAsync(uri).ConfigureAwait(false);
        return JObject.Parse(jsonString);
      }
    }

    이렇게 하면 GetJsonAsync의 연속 동작이 변경되어 컨텍스트에서 다시 시작되지 않습니다.

    대신 GetJsonAsync는 스레드 풀 스레드에서 다시 시작된다. 이렇게 하면 GetJsonAsync가 컨텍스트를 다시 입력하지 않고 반환 한 작업을 완료 할 수 있습니다.


    교착 상태를 피하기 위해 ConfigureAwait(false)를 사용하는 것은 위험한 방법이다

    모든 서드파티 등의 코드들이 차단 코드에 의해 호출되는 모든 메소드의 전이 클로저에서 기다리는 동안 COnfigureAwait(false)를 사용해야 한다. 

    어쨌든 더 다은 해결책은 아래의 방법을 사용한다.


    2. 작업을 차단하지 말고 비동기를 계속 사용한다.(위으 5번 작업을 기다리는 행위)

    public async void Button1_Click(...)
    {
      var json = await GetJsonAsync(...);
      textBox1.Text = json;
    }
    
    public class MyController : ApiController
    {
      public async Task<string> Get()
      {
        var json = await GetJsonAsync(...);
        return json.ToString();
      }
    }

    이렇게하면 최상위 수준 메서드의 차단 동작이 변경되어 컨텍스트가 실제로 차단되지 않는다.

    모든 대기는 비동기 대기다.

    (jsonTask.Result의 차단에서 json.ToString()으로 차단하지 않게 했다는 점이 ㅈ중요한것.)


    두 가지 사례를 모두 적절히 적용하는것이 가장 중요하다고 함..




    '@ 17. 1 ~ 18 > C# 멀티스레드' 카테고리의 다른 글

    읽기 / 쓰기 락  (0) 2018.06.27
    스레드 로컬 저장소  (0) 2018.06.21
    스레드 동기화란?  (0) 2018.03.05
    동시성 컬렉션 사용  (0) 2017.08.20
    async, await에 대한 설명  (0) 2017.08.19
Designed by Tistory.