모든언어에서 중요한 부분인 멀티 쓰레드, 비동기 프로그래밍에 대해 약간 고찰해보는 시간을 가진다.
보통 코드는 순차적으로 실행된다.
하지만 여러가지 작업을 동시에 수행하고 싶을 때 비동기 프로그래밍을 사용한다.
예를 들어 UI에서 버튼을 클릭하면 덧셈 연산을 1만번하는 프로그래밍이 있다고 하자.
동기 프로그래밍이라면 1만번 연산 할 동안 화면은 정지된다.
비동기 프로그래밍은 버튼을 누르면 연산을 담당하는 담당자 쓰레드가 해당 작업을 진행하고
(멀티쓰레드 비동기일 때 기준! / 싱글 쓰레드 비동기의 경우 (자바스크립트) 작업을 돌아가면서 진행하거나 외부로 넘기거나 한다..)
메인 쓰레드는 UI 반응을 감지하며 화면이 정지되지 않게 한다.
일꾼이라고 생각하면 된다.
기본은 메인쓰레드로 일꾼이 1명이여서 혼자서 여러가지 일을 동시에 할 수 없다.
멀티쓰레딩 작업을 진행하면 일꾼 자체가 늘어나서 여러가지 일을 맡길 수 있다.
설명서: https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/asyn
async - C# 참조 - C#
async - C# 참조
learn.microsoft.com
async 키워드를 메서드, 람다 식, 무명 메서드에 붙이면 비동기 메서드로 지정할 수 있다.
메소드 내에 await 키워드를 포함시켜야 한다.!!
(포함시키지 않아도 돌아가긴하는데 경고가 발생한다.)
반환 값:
많이 놓칠 수 있는 부분이라고 생각한다.
async를 붙이면 끝이 아니고 추가로 반환 타입을 정해줘야 하는데
Task
Task<TResult>
void
타입으로 반환이 가능하다.
또한 in, ref, out의 사용이 불가능 하다.
그냥 Task만 반환하는 경우가 void라고 생각하면될 것 같고
반환 타입이 필요하다면 Task<TResult>형식으로 사용한다.
void는 주로 이벤트 핸들러에서 사용한다고 한다. 다만 해당 메소드에 대해 await가 불가능하고 예외 포착이 불가능하다.
비동기 프로그램의 시작부분이다.
먼저 이 코드를 돌려보자
static async Task CheckAsync()
{
Console.WriteLine("1메인! 현재 쓰레드 번호 :{0}", Thread.CurrentThread.ManagedThreadId);
Run();
Console.WriteLine("2메인! 현재 쓰레드 번호 :{0}", Thread.CurrentThread.ManagedThreadId);
}
private static async Task Run()
{
Console.WriteLine("함수 내부! 현재 쓰레드 번호 :{0}",Thread.CurrentThread.ManagedThreadId);
sum = await NewTask(3);
Console.WriteLine("await 이후! 현재 쓰레드 번호 :{0} sum : {1}", Thread.CurrentThread.ManagedThreadId, sum);
}
private static async Task<int> NewTask(int num)
{
int temp = 0;
for (int i = 0; i < 2; i++)
{
temp += i;
Thread.Sleep(1000);
}
Console.WriteLine("await로 실행된 함수! 현재 쓰레드 번호 :{0}", Thread.CurrentThread.ManagedThreadId);
return num;
}
async와 await를 사용했지만 메인쓰레드가 계속 작업을 진행했다.
핵심 부분은 await NewTask(3); 인데 함수를 await하니 그냥 동기프로그램으로 진행되었다.
당연하다!
await 키워드는 기다린다는 의미로 비동기 프로그램에서 코드 순서를 순차적으로 진행하기 위해 사용하는 키워드이다!
해당 코드는 비동기 키워드를 많이 사용했지만 동기프로그램처럼 동작하고 있다.
그렇다면 위 코드에서 NewTask를 비동기로 실행하고 싶다면 어떻게 해야할까?
private static async Task Run()
{
Console.WriteLine("함수 내부! 현재 쓰레드 번호 :{0}",Thread.CurrentThread.ManagedThreadId);
Task.Run(() => { NewTask(3); });
Console.WriteLine("await 이후! 현재 쓰레드 번호 :{0} sum : {1}", Thread.CurrentThread.ManagedThreadId, sum);
}
Run 메서드를 조금 변경하였다.
Task.Run을 사용하여 새로운 Task를 실행시켰고 그 결과
비동기 함수 NewTask가 실행되는 동안 메인쓰레드가 할 일을 전부 마무리했고 그 뒤에 NewTask가 마무리되는 모습이다.
중요한 점은 async, await가 있다고 해서 무조건 비동기가 아니라는 점이다!
또한 비동기 사용 시 await를 사용하지 않는다면 메소드 종료 시점 관리가 불가능하기 때문에 매우 위험하다.
이런 코드를 실행시켜보자
static void Main(string[] args)
{
Task taskA = new Task(() => Console.WriteLine("Hello from taskA."));
taskA.Start();
for (int i = 0; i < 80; i++)
{
Console.WriteLine("Hello from thread '{0}'.",
Thread.CurrentThread.Name);
}
Console.ReadLine();
}
어느 시점에 Task가 실행될지 알 수 없게 된다...
정리하자면
async : 비동기 메서드 선언 키워드
await : 비동기 메서드가 실행될 때까지 대기한다.
task : 비동기 작업 그 자체
Task.Run(Action)
Task t = new Task(Func);
Task.Start();
처럼 사용이 가능하고 비동기로 실행된다.