C# Asynchronous(비동기)

2 분 소요

Asynchronous (비동기)

C# 에 비동기에 대해서 알아 보도록 하자.

Async / Await 의 Best Practice

1. Async Void는 피하자.

비동기 메서드는 Task, Task<T>, Void의 반환 형식이 있지만, Void 반환 형식은 EventHandler를 제외 하고는 사용하지 않는다.
Void 반환의 예외처리는 Task, Task<T>와 다르다.

private async void ThrowExceptionAsync()
{
  throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here!
    throw;
  }
}

Task 반환 형식의 비동기 호출은 예외가 발생하면 캡쳐 해서 Task에 할당 한다.
하지만 Task가 없는 Void 반환 형식은 void 메서드가 시작될 때 활성화 된 SynchronizationContext 에서 직접 발생 한다. 그래서 호출한 Void Method에서는 Catch 할 수 없는 것이다.
그렇지만 우리는 분명히 Async Void를 사용할 일이 있다. EventHander는 대부분 Void이기 때문이다.
그렇다면 Exception Catch를 희생하지 않고 어떻게 실행 할 수 있을까?

Async Void Method 에서 Exception Catch

private async void button1_Click(object sender, EventArgs e)
{
  await Button1ClickAsync(); // await 키워드를 사용 한다.
}

public async Task Button1ClickAsync() //Task를 반환 한다.
{
  // Do asynchronous work.
  await Task.Delay(1000);
}

다시한번 확인 하지만, 우리는 EventHandler를 제외 하고 모든 비동기 처리는
Task 반환 타입으로 구성해야 합니다.
이유는 위에서 확인 했듯이, 오류 처리, 구성 가능성 및 테스트 가능성이 더 쉬워지기 때문입니다.(선택은 자유 ^^)

2. 동기 코드와 비동기 코드 혼합 사용 주의

먼저 아래의 예제를 살펴 보자.
일반적인 교착 상태 문제

public static class DeadlockDemo
{
  private static async Task DelayAsync()
  {
    await Task.Delay(1000); //Work Thread가 UI Thread를 대기
  }
  // This method causes a deadlock when called in a GUI or ASP.NET context.
  public static void Test()
  {
    // Start the delay.
    var delayTask = DelayAsync();
    // Wait for the delay to complete.
    delayTask.Wait();
  }
}

ConfigureAwait의 사용하여 교착상태 문제 해결

async Task MyMethodAsync()
{
  // Code here runs in the original context.
  await Task.Delay(1000);
  // Code here runs in the original context.
  await Task.Delay(1000).ConfigureAwait(
    continueOnCapturedContext: false);
  // Code here runs without the original
  // context (in this case, on the thread pool).
}

continueOnCapturedContext가 true인 경우는 Capture한 Context를 Task 완료 후 계속해서 사용 하겠다는 의미이다. false면 Thread Pool에서 새롭게 할당 받아서 다음 코드로 진행 된다. 자연스럽게 교착상태를 피할 수 있다.

하지만 Task가 완료 된 후 UI Context를 사용해야 한다면, ConfigureAwait()는 사용할 수 없다.

그래서 아래와 같이 사용 해야 한다.

private async Task HandleClickAsync()
{
  // Can use ConfigureAwait here.
  await Task.Delay(1000).ConfigureAwait(continueOnCapturedContext: false);
}
private async void button1_Click(object sender, EventArgs e)
{
  button1.Enabled = false;
  try
  {
    // Can't use ConfigureAwait here.
    await HandleClickAsync();
  }
  finally
  {
    // We are back on the original context for this method.
    button1.Enabled = true;
  }
}

댓글남기기