C# 5.0 비동기 프로그래밍 개요

C# 5.0부터는 비동기 오퍼레이션을 위한 두개의 키워드 async와 await가 추가되었다. 이 키워드를 이용하여 간단한 코드로 Task 병렬 라이브러리를 이용해 네트워크 억세스와 같이 별도의 쓰레드에서 비교적 긴시간이 걸리는 작업에 대한 결과를 쉽게 얻을 수 있다.

개요

이 문서는 Xamarin.iOS와 Xamarin.Android를 기준으로 간단한 예제를 구현하고 있으며, 좀 더 상세한 정보는 MSDN문서 Async와 Await를 이용한 비동기 프로그래밍를 참고하기 바란다.
샘플 어플리케이션은 메인 쓰레드에 대한 간섭(Blocking)없이 간단한 비동기 웹요청을 보내고 그 결과값을 UI에 반영하는 과정을 보여준다.
결과화면

C# 5.0은 Mono 3.0 이상을 요구하며, Xamarin.iOS 6.4, Xamarin.Android 4.8 이상에 포함되어 있다. 최신 버전의 Xamarin Studio로 업그래이드 하면 사용이 가능하다.

async

async 키워드는 메소드 정의시에 사용된다. 람다 또는 익명 메소드에도 사용될 수 있다. 이것은 해당 메소드가 비동기 코드를 포함하고 있다는것을 의미한다. 즉, 호출자의 쓰레드를 블록킹하지 않도록 한다는것을 의미한다. 그러므로, async 메소드는 반드시 하나 이상의 await 구문 또는 표현식을 포함하여야 한다. 만일, await 구문이 없다면 컴파일러 경고가 발생하며 심각한 에러가 발생하지는 않는다.

반환값

async 메소드는 Task, Task 또는 void 타입을 리턴한다.

  • 메소드가 어떤 값도 리턴하지 않는다면 Task형을 리턴 하도록 한다.
  • 값을 리턴할 필요가 있다면 Task형을 리턴 하도록 한다. TResult는 타입명이다.
  • void 리턴 타입은 주로 이벤트 핸들러의 경우에 사용된다. 이 코드는 결과를 기다리지 않는다.

await

await 오퍼레이터는 해당 작업을 비동기로 구분한다. 이 지점에 메소드의 실행이 멈추고 작업이 끝나기를 기다리게 된다. await의 사용은 호출 쓰레드까지 멈추게 하지 않고 제어권을 호출자에게 넘긴다. 일반적으로 호출자는 UI 쓰레드인 경우가 많으며 태스크를 기다리는 동안에도 사용자 인터페이스는 블록킹이 되지 않도록 해준다. 작업이 종료되면, 메소드는 다시 원래 지점으로 돌아와 실행을 계속한다. await는 try-catch-finally 구문에서 try 블록 내에서만 사용이 가능하며 catch나 finally 블록에서는 사용될 수 없다. await에 대한 더 상세한 정보는 await on MSDN를 참고하라.

취소

완료하는데 오랜 시간이 걸리는 비동기 메소드는 취소(cancellation)를 지원해야 한다. 일반적으로, 취소는 다음과 같은 순서로 실행된다.

  1. CancellationTokenSource 객체를 생성한다.
  2. CancellationTokenSource.Token 인스턴스가 취소 가능한 비동기 메소드로 전달된다.
  3. CancellationTokenSource.Cancel 메소드를 호출하여 취소를 요청한다.

취소에 대한 상세한 정보는, MSDN의 how to cancel an asynchronous task 문서를 참고하라.

예제

예제소스를 통해 async 와 await 의 모바일에서 실사용 예를 살펴보자.

async 메소드 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public async Task<int> DownloadHomepage()
{
var httpClient = new HttpClient(); // Xamarin supports HttpClient!
Task<string> contentsTask = httpClient.GetStringAsync("http://xamarin.com"); // async method!
// await! control returns to the caller and the task continues to run on another thread
string contents = await contentsTask;
ResultEditText.Text += "DownloadHomepage method continues after async call. . . . .\n";
// After contentTask completes, you can calculate the length of the string.
int exampleInt = contents.Length;
ResultEditText.Text += "Downloaded the html and found out the length.\n\n\n";
ResultEditText.Text += contents; // just dump the entire HTML
return exampleInt; // Task<TResult> returns an object of type TResult, in this case int
}
  • 이 메소드 선언은 async 키워드를 포함한다.
  • 반환 타입은 Task<int>이다. 즉, 호출자는 정수값을 받을 수 있다.
  • 리턴 구문은 return exampleInt; 이다.

async 메소드 호출 1

다음 코드는 안드로이드 샘플 소스에서 발췌한 버튼 클릭에 대한 이벤트 핸들러 이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GetButton.Click += async (sender, e) => {
Task<int> sizeTask = DownloadHomepage();
ResultTextView.Text = "loading...";
ResultEditText.Text = "loading...\n";
// await! control returns to the caller
var intResult = await sizeTask;
// when the Task<int> returns, the value is available and we can display on the UI
ResultTextView.Text = "Length: " + intResult ;
// "returns" void, since it's an event handler
};

  • 익명의 딜리게이트는 이벤트 핸들러에 비동이 메소드임을 나타내는 async 키워드를 사용할 수 있다.
  • DownloadHomepage 는 비동기 메소드로서 Task를 리턴하고, 이것을 sizeTask 변수에 저장한다.
  • sizeTask에 대한 await코드는 메소드가 실제로 정체되는 구간으로 제어가 호출자로 돌아가는 부분이다. 이것은 비동기 작업이 현재 쓰레드로 돌아왔을때 다시 재개된다.
  • Task가 앞부분에 선언되었다고 해서 메소드가 정체되지 않는다. 반면, Task는 await 키워드가 노출된 부분부터 실행이 정체된다.
  • 비동기 작업이 완료되었을뗘 , intResult값이 할당되고 원래 쓰레드로 돌아온다.

async 메소드 호출 2

+= HandleTouchUpInside
1
2
3
4
5
6
7
8
9
10
11
12
...
async void HandleTouchUpInside (object sender, EventArgs e)
{
ResultLabel.Text = "loading...";
ResultTextView.Text = "loading...\n";
// await! control returns to the caller
var intResult = await DownloadHomepage();
// when the Task<int> returns, the value is available and we can display on the UI
ResultLabel.Text = "Length: " + intResult ;
}

TODO: summary

출처 : https://developer.xamarin.com/guides/cross-platform/advanced/async_support_overview/