공부/인프런 - Rookiss

Part 6-3-1. 고급 C# 문법 : Async, Await

셩잇님 2024. 8. 17. 17:04
반응형

 

 

🚀 고급 C# 문법

 

 지난 시간에는 CSS의 Box Model을 학습하며 Margin, Border, Padding 등 컨텐츠의 표시 영역에 관한 내용에 대해서 학습하였다. 이번 시간부터는 고급 C# 문법 중 하나인 Async, Await에 대해서 학습하도록 하자.

 


 

👑 Async, Await

 

 Async, Await는 Part.4 게임 서버를 수강했다면 알 수 있는 비동기 프로그래밍 문법임을 바로 알 수 있다. 하지만 꼭 '비동기= 멀티스레드'의 개념이 성립하는 것은 아니다. 예를 들어 유니티의 코루틴만 보더라도 일종의 비동기로 동작하지만, 이는 싱글 스레이드이기 때문이다.

 

 실상에서 예를 든다면 우리가 커피숍을 간다고 생각해보자. 매장에 방문하면 커피를 만들어주는 직원도 있고, 주문을 받는 직원이 있다. 하지만 경우에 따라 손님은 너무 많지만 직원이 적을 경우 각각의 직원이 이를 병행하는 모습을 볼 수 있다. 아이스 아메리카노를 만들던 직원이 손님이 오면, 응대를 위해 커피를 만드는 행위를 멈추고, 주문을 받은 다음에 다시 커피를 만드는 것과 동일한 것이다. 따라서 한 명이 여러가지 일을 동시다발적으로 하는 경우가 있을 수 있으므로 '비동기 = 멀티스레드'의 개념은 항상 성립하는 것은 아니다.

 


 

Task

 

Async, Await을 이해하기 위해서는 먼저 'Task'의 개념을 선행해서 이해해야 한다. Task를 직독직해하자면 이는 '일감'이라는 뜻이다. 즉 우리는 앞으로 일감(Task)을 만들고 이를 경우에 따라 다른 사람(멀티스레드) 혹은 내가(싱글스레드) 우선순위에 따라 일을 처리하는 것을 알 수 있다. Task의 간단한 예를 살펴보자.

 

using System;

namespace AdvanceSyntax
{
    class Program
    {
        static Task Test()
        {
            Console.WriteLine("Start Test");
            Task t = Task.Delay(3000);
            return t;
        }

        static async Task Main(string[] args)
        {
            Task t = Test();

            Console.WriteLine("while start");

            while (true)
            {

            }
        }
    }
}

 

 먼저, Task를 새롭게 만들어 주도록 하자. Test Task는 3초 후에 일이 완료가 되도록 정의하고, 이를 테스트하기 위해 메인함수에서 해당 함수를 실행하도록 한다. 여기에서 궁금한 점은 '3초 기다리는 행위' 그 자체가 Task를 만드는 순간 시작이 되는가?이다. 이를 확인해보기 위해 실행해보도록 하자.

 

Test Task의 결과

 

 실행 결과를 분석한다면 어떻게 프로그램 동작 방식을 유추할 수 있을까? 먼저 프로그램을 실행하면 Task t = Test()를 통해 기다리는 행위 자체는 시작이 된 것을 알 수 있다. 왜냐하면 Start Test 로그가 먼저 나타났기 때문이다. 하지만 3초를 기다리는 행위를 완료했는지, 안했는지의 문제는 또 다른 별개의 문제로 해석할 수 있다.

 

 그렇다면 3초를 기다리는 행위를 무조건 끝날 때까지 기다리고 싶다.는 어떻게 처리하면 좋을까? 바로 t.Wait()을 통해 해당 일감이 끝났는지 안끝났는지를 알 수 있다. 해당 내용을 추가해보도록 하자.

 

using System;

namespace AdvanceSyntax
{
    class Program
    {
        ...

        static async Task Main(string[] args)
        {
            Task t = Test();
            t.Wait();

            Console.WriteLine("while start");

            while (true)
            {

            }
        }
    }
}

 

t.Wait()의 결과

 

 실행 결과는 같지만, t.Wait()을 통해 3초 기다렸다가 while start 로그가 뜬 것을 알 수 있다. 그렇다면 이를 통해 알 수 있는 결론은 무엇이 있을까? 바로 기존에 우리가 사용하던 형태의 코드들은 Task Test 함수가 완료될 때 까지 기다렸다가 그 이후 다음 부분이 실행되었지만, 위에서 사용한 Task 형태로 함수를 작성하게 될 경우 반환되기 전 까지 추가로 하고 싶은 작업을 처리할 수 있다는 것이다. 이를 코드로 설명하자면 아래와 같다.

 

using System;

namespace AdvanceSyntax
{
    class Program
    {
        ...

        static async Task Main(string[] args)
        {
            Task t = Test();
            
            /*
             * Task Test()가 끝나기 전까지 다른 일을 추가적으로 할 수 있다.
             * ex) int a = 1; :)
             */

            Console.WriteLine("while start");

            while (true)
            {

            }
        }
    }
}

 

 이를 통해 Task는 '일감'을 만들어가지고 보내준다와 같이 간략하게 이해할 수 있다.

 


 

 이제 다음으로 비동기 형태 (Asynchronous)를 테스트 해 보도록 하자. 코드는 아래와 같다.

 

using System;

namespace AdvanceSyntax
{
    class Program
    {
        static void TestAsync()
        {
            Console.WriteLine("Start TestAsync");
            Task t = Task.Delay(3000);
            t.Wait();
            Console.WriteLine("End TestAsync");
        }

        static async Task Main(string[] args)
        {
            TestAsync();

            Console.WriteLine("while start");

            while (true)
            {

            }
        }
    }
}

 

 코드는 아까와 동일하다. 단 이번에는 t.Wait()을 TestAsync 함수 내부에서 처리하도록 한다. 또한 해당 함수는 아무것도 반환하지 않으므로 void 형태로 만들어주도록 한다. 위와 같이 코드를 작성한 다음 실행해보도록 하자.

 

3초를 기다리면 End TestAsync가 나타난다.

 

 실행 결과는 이전과 다를 것이 없다. 3초 기다릴 경우 End TestAsync가 나타나고, while start 로그가 찍히는 것을 볼 수 있다. 여기까지는 딱히 어려운 내용은 없다.

 

 그렇지만 생각해보자. t.Wait()이라는 함수를 내부에서 처리하는 방법이 과연 옳은 방법일까? 만약 Task t = Task.Delay(3000)이 아닌 보다 복잡한 작업 (예 DB, 혹은 파일작업)일 경우에는 해당 프로세스가 정말 오래걸려 끝나지 않을 경우 외부(Main 함수)로 나갈 수 없어서 시스템이 먹통이 되는 현상이 발생한다.

 

 따라서 이를 해결하기 위해 '무조건 기다리는 것이 아닌, 기다리긴할테지만 기다리는 동안 너는 너 할거 해'와 같이 코드 제어권을 외부(Main 함수)으로 넘겨줄 경우 보다 쉽게 해결할 수 있다. 이제 이를 알아보도록 하자.

 


 

Async, Await

 

using System;

namespace AdvanceSyntax
{
    class Program
    {
        static async void TestAsync()
        {
            Console.WriteLine("Start TestAsync");
            Task t = Task.Delay(3000);
            await t;
            // 이는 await Task.Delay(3000);으로 축약할 수 있다.
            Console.WriteLine("End TestAsync");
        }

        static async Task Main(string[] args)
        {
            TestAsync();

            Console.WriteLine("while start");

            while (true)
            {

            }
        }
    }
}

 

 위에서 사용하던 t.Wait()을 지우고 이제는 wait앞에 a를 붙여서 await를 사용하도록 하자. 또한 await을 사용하기 위해서는 'async'를 함수에 붙여줘야 사용할 수 있음을 명심해야 한다.

 

 TestAsync 함수를 위와 같이 수정할 경우 3초 기다려야하는 부분이 끝나야 End TestAsync 로그가 뜨는 것은 같지만, 다만 무조건 TestAsync 내부에서 3초 기다려야 하는 부분이 달라진다. 그 전까지는 내부에서 무조건 기다렸지만, await을 사용할 경우 내부에서 이를 기다리는 것이 아닌, 외부(Main 함수)로 제어건을 넘겨주게 되는 것이다. 해당 예제를 실행해보자.

 

await을 통한 제어권 전달

 

 실행 결과를 살펴보면 알 수 있듯이 이제는 End TestAsync의 로그가 뜨기 전에 while start가 뜨는 것을 볼 수 있다. 즉 함수의 실행 플로우가 아래와 같다는 것이다.

 

 

 이를 통해 알 수 있는 것은 비동기를 사용한다면 함수 내부에서 작업이 완료되는 것을 끝날 때까지 기다리지 않고, 바로 빠져나와 다른 함수를 실행할 수 있다는 것이다. 이것이 비동기의 핵심이다. 아울러 Task도 일반 함수와 동일하게 값을 반환할 수 있다. 아래 예제를 통해 값을 반환하는 Task를 만들어보자.

 

 

using System;

namespace AdvanceSyntax
{
    class Program
    {
        static async Task<int> TestAsync4()
        {
            Console.WriteLine("Start TestAsync");
            await Task.Delay(3000);
            Console.WriteLine("End TestAsync");
            return 100;
        }

        static async Task Main(string[] args)
        {
            int ret = await TestAsync();

            Console.WriteLine("while start");
            Console.WriteLine(ret);

            while (true)
            {

            }
        }
    }
}

값을 반환하는 것을 확인할 수 있다.

 


 

 이 예제를 통해 비동기 전체를 완벽하게 이해하는 것은 쉽지 않을 것이다. 따라서 마이크로소프트에서도 비동기 처리에 대한 이해를 위한 공식 도큐먼트가 있으니, 이 또한 참조하도록 하자.

 

https://learn.microsoft.com/ko-kr/dotnet/csharp/asynchronous-programming/async-scenarios

 

비동기 프로그래밍 시나리오 - C#

.NET Core에서 제공하는 C# 언어 수준 비동기 프로그래밍 모델에 대해 알아봅니다.

learn.microsoft.com

https://learn.microsoft.com/ko-kr/dotnet/csharp/asynchronous-programming/

 

C#의 비동기 프로그래밍 - C#

async, await 및 Task를 사용하여 비동기 프로그래밍을 지원하는 C# 언어에 대해 간략히 설명합니다.

learn.microsoft.com

 

반응형