메뉴 건너뛰기

Xamarin Dev

SHARING, PUBLISHING.
& PLEASURE.

지식을 나누고 컨텐츠를 출판하며 즐거움을 함께합니다.

MAKING
WEB CULTURES

올바른 웹 문화를 지향합니다.

EVOLUTION & INNOVATION
TOGETHER

함께 진화하고 혁신을 추구합니다.

CREATE A GOOD DESIGN WITH
THE POSSIILITY OF TECHNOLOGY

기술의 가능성을 발굴하고 좋은 디자인을 만들어 갑니다.

 

 

출처 : http://www.csharpstudy.com/CSharp/CSharp-async-await.aspx

 

C# 5.0 : async / await 키워드 
 
C# 5.0부터 새로운 C# 키워드로 async와 await가 추가되었다. 이 키워드들은 기존의 비동기 프로그래밍 (asynchronous programming)을 보다 손쉽게 지원하기 위해 C# 5.0에 추가된 중요한 기능이다.

C# async는 컴파일러에게 해당 메서드가 await를 가지고 있음을 알려주는 역활을 한다. async라고 표시된 메서드는 await를 1개 이상 가질 수 있는데, 하나도 없는 경우라도 컴파일은 가능하지만 Warning 메시지를 표시한다. async를 표시한다고 해서 자동으로 비동기 방식으로 프로그램을 수행하는 것은 아니고, 일종의 보조 역활을 하는 컴파일러 지시어로 볼 수 있다.

실제 핵심 키워드는 await인데, 이 await는 일반적으로 Task 혹은 Task<T> 객체와 함께 사용된다. Task 이외의 클래스도 사용 가능한데, awaitable 클래스, 즉 GetAwaiter() 라는 메서드를 갖는 클래스이면 함께 사용 가능하다.

await는 Task와 같은 awaitable 클래스 객체가 완료되기를 기다리는데, 여기서 중요한 점은 UI 쓰레드가 정지되지 않고 메시지 루프를 계속 돌 수 있도록 필요한 코드를 컴파일러가 await 키워드를 만나면 자동으로 추가한다는 점이다. 메시지 루프가 계속 돌게 만든다는 것은 마우스 클릭이나 키보트 입력등을 계속 처리할 수 있다는 것을 의미한다. await는 해당 Task가 끝날 때까지 기다렸다가 완료 후, 바로 다음 실행문부터 실행을 계속한다. await가 기다리는 Task 혹은 실행 메서드는 별도의 Worker Thread에서 돌 수도 있고, 또는 UI Thread에서 돌 수도 있다. 즉, await가 항상 비동기 실행을 위한 Background Thread를 필요로 하는 것은 아니다. await가 보장하는 것은, Task가 UI Thread에 돌던지, Worker thread에서 돌던지 상관없이 Task 완료 후 await 이후의 실행문들을 디폴트로 원래 await를 실행하기 전의 Thread에서 실행하도록 보장하는 것이다.

아래 예제는 버튼 클릭으로 Run()이라는 async 메서드를 실행하고, Run 메서드 안에서 비동기 Task를 만들어 실행하고 결과를 기다리는 await 문의 예를 보여주고 있다. await는 LongCalcAsync() 라는 메서드가 끝나기를 기다렸다가 끝나면 결과를 sum에 할당한 후 다음 문장들을 계속 실행한다. 특히 여기서 주목할 만한 것은 결과값을 Label 컨트롤에 뿌려줄 때, Invoke()나 BeginInvoke()를 쓸 필요가 없다는 점이다. Background Thread에서 비동기 Task가 끝난 후, await가 다시 Caller가 갖고 있던 쓰레드 즉 UI Thread로 다음 문장들을 실행하게 하기 때문이다.
 
 

예제

private void button1_Click(object sender, EventArgs e)
{
     Run();  //UI Thread에서 실행
}

private async void Run()
{
    // 비동기로 Worker Thread에서 도는 task1
    var task1 = Task<int>.Run(() => LongCalcAsync(10));

    // task1이 끝나길 기다렸다가 끝나면 결과치를 sum에 할당
    int sum = await task1;

    // UI Thread 에서 실행
    // Control.Invoke 혹은 Control.BeginInvok 필요없음
    this.label1.Text = "Sum = " + sum;
    this.button1.Enabled = true;
}

private int LongCalcAsync(int times)
{
    // ThreadPool에서 Worker Thread를 가져와
    // 아래 문장들 실행함
    int result = 0;
    for (int i = 0; i < times; i++)
    {
        result += i;
        Thread.Sleep(1000); 
    }
    return result;
}
 

.NET 4.5 Async 혹은 TaskAsync 메서드들 
 
C# 5.0과 함께 선보인 .NET 4.5는 기존의 동기화(Synchronous) 메서드들과 구분하여 C#의 await (혹은 VB의 Await)를 지원하기 위해 많은 Async 메서드들을 추가하였다. 이 새 메서드들은 기본적으로 기존의 Synchronous 메서드명 뒤에 Async를 붙여 명명되었는데, 만약 기존에 Async로 끝나는 함수가 이미 있었던 경우에는 TaskAsync를 메서드명에 붙여 명명하였다. 
 

 

await : UI 쓰레드에서 도는 Task 
 
await가 기다리는 Task는 항상 Background Thread에서 돌 필요는 없다. 물론 많은 경우 Background Thread에서 실행될 것이지만... 다음 예제는 Task가 UI 쓰레드에서 실행되는 예이다. (물론 이러한 경우에도 중간에 Background Thread에서 도는 문장들을 넣는 것이 일반적이다.) 하나의 CPU에서 멀티태스킹이 가능하듯이 하나의 UI Thread에서도 시분할을 통해 비동기 실행이 가능하다. 
 

예제

private void button1_Click(object sender, EventArgs e)
{
     Run();  //UI Thread에서 실행
}

private async void Run()
{    
    int sum = await LongCalc2(10);
    this.label1.Text = "Sum = " + sum;
    this.button1.Enabled = true;
}

private async Task<int> LongCalc2(int times)
{
    //UI Thread에서 실행
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
    int result = 0;
    for (int i = 0; i < times; i++)
    {
        result += i;                
        await Task.Delay(1000);
    }
    return result;
}

await : Task.ContinueWith() 
 
앞에서 이야기 하였듯이 await는 해당 Task가 끝난 후 await 문장이 있었던 곳으로부터 계속 다음 문장들을 실행하도록 되어있다. 이러한 기능은 .NET 4.0에서 소개 되었던 Task클래스의 ContinueWith()를 써서 아래와 같이 구현될 수 있다. 물론 C# 5.0 컴파일러가 await를 이렇게 변경한다는 것은 아니지만, 개념적으로 동일한 방식이라 볼 수 있다.

아래 예제에서 ContinueWith() 메서드는 첫번째 파라미터에서 task1 이 끝난 후 실행될 명령들을 람다식으로 지정하고 있다. 그리고 두번째 파라미터에는 실행블럭이 현재 쓰레드 (예제의 경우 UI Thread)에서 실행하도록 TaskScheduler.FromCurrentSynchronizationContext()를 지정하고 있다. 즉, 개념적으로 await는 특정 Task가 실행된 후 이러 이러한 실행블럭을 현재 실행 (쓰레드) 컨텍스트에서 실행하도록 하는 것이다. 

 

예제

private async void Run2()
{    
    var task1 = Task<int>.Run(() => LongCalc2(10));
    
    // await task1과 동일한 효과
    //
    task1.ContinueWith(x => {
      this.label1.Text = "Sum = " + task1.Result;
      this.button1.Enabled = true;      
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

위로