C# Async Await 를 사용한 비동기 프로그래밍

C# Async Await는 C# 5.0에서 처음 소개되었습니다. 이들은 코드가 실행되는 동안 백그라운드에서 비동기적으로 실행될 수 있는 코드를 간편하게 작성할 수 있도록 설계되었습니다.

Async 및 Await

async 및 await 키워드는 C#에서 10년 이상 사용되어 왔지만 이 구현은 기존 방식과는 상당히 다른 매우 새로운 접근 방식입니다.

비동기 프로그래밍은 호출 스레드의 블로킹 없이 코드를 동시에 실행할 수 있는 프로그래밍 기법입니다. 이는 주로 I/O나 네트워크와 같은 시간이 오래 걸리는 작업을 수행할 때 성능을 향상시키는 데 사용됩니다.

비동기 프로그래밍은 여러 기술을 사용하여 구현할 수 있으며, C#의 “async”와 “await” 키워드는 이를 훨씬 간단하게 만들어 줍니다.

Async Await 개념

async await 키워드가 사용되어진 메서드는 현재 실행 스레드와 다른 스레드에서 실행된다라고 이해 하시면 됩니다.
즉 메서드 하나를 비동기적으로 실행시킬때 스레드 생성해서 따로 실행시켜주는 번거로운 작업을 ayns await 로 아주 쉽게 할 수 있다는 장점입니다.

아래 실행 되는 예제를 한번 보시겠습니다.

class Program
{
    static public void Main()
    {
        Test();
        Console.WriteLine("Done");
        Console.Read();
    }

    static async void Test()
    {
        Console.WriteLine($"[{DateTime.Now}] Test 1: threadid == {Thread.CurrentThread.ManagedThreadId}");

        var x = Task.Run(() =>
        {
            Console.WriteLine($"[{DateTime.Now}] Test 2: threadid == {Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(1000);
        });
        await x;

        Console.WriteLine($"[{DateTime.Now}] Test 3: threadid == {Thread.CurrentThread.ManagedThreadId}");
    }
}

출력은 다음과 같습니다.

[2024-03-14 오전 10:47:48] Test 1: threadid == 1
[2024-03-14 오전 10:47:48] Test 2: threadid == 9
Done
[2024-03-14 오전 10:47:49] Test 3: threadid == 9

위 사항을 스레드 ID 로 분석해 보면 async 가 붙은 메서드내에서 await 를 만난 이후 부터는 다른 스레드에서 실행되고 있음을 알 수 있습니다.
그렇듯 async 와 await 는 쌍으로 움직입니다.
async 가붙은 메서드내에서 await 이후부터 다른스레드에서 실행된다 가 기본 공식입니다.

그렇다면 aysnc 가 붙은 함수내에 await 가 없다면 어떻게 될까요?

class Program
{
    static public void Main()
    {
        Test();
        Console.WriteLine("Done");
        Console.Read();
    }

    static async void Test()
    {
        Console.WriteLine($"[{DateTime.Now}] Test 1: threadid == {Thread.CurrentThread.ManagedThreadId}");

        Thread.Sleep( 1000 );

        Console.WriteLine($"[{DateTime.Now}] Test 2: threadid == {Thread.CurrentThread.ManagedThreadId}");
    }
}
[2024-03-14 오전 10:53:11] Test 1: threadid == 1
[2024-03-14 오전 10:53:12] Test 2: threadid == 1
Done

출력문을 보면 async 가 붙은 메서드이지만 동기로 실행되고 있습니다.

이렇듯 async 와 await 는 반드시 쌍으로 다녀야 합니다.

비동기 프로그래밍 예시

C#에서 비동기 작업을 예시를 통해 살펴보겠습니다.

예시 1

서로 의존하지 않는 두 개의 메서드를 호출하는 간단한 예시입니다.

class Program
{
    static void Main(string[] args)
    {
        Method1();
        Method2();
        Console.ReadKey();
    }

    public static async Task Method1()
    {
        await Task.Run(() =>
        {
            for (int i = 0; i < 100; i++)
            {
                Console.WriteLine(" Method 1");
                // 무언가 수행
                Task.Delay(100).Wait();
            }
        });
    }

    public static void Method2()
    {
        for (int i = 0; i < 25; i++)
        {
            Console.WriteLine(" Method 2");
            // 무언가 수행
           Task.Delay(100).Wait();
        }
    }
}

출력문은 다음과 같습니다.

Method 1
Method 2
Method 1
Method 2
Method 1
Method 2
Method 1
Method 2
Method 2
Method 1
Method 2
Method 1
Method 2
Method 1
Method 1
Method 2
Method 2
Method 1
Method 1
Method 2

이는 비동기 함수(Method1)와 동기 함수(Method 2)가 서로 경합을 벌이며 출력을 하고 있습니다.

예시 2

Method1에 의존하는 Method3이 있는 예시입니다.

class Program
{
    static async Task Main(string[] args)
    {
        await CallMethod();
        Console.ReadKey();
    }

    public static async Task CallMethod()
    {
        Method2();
        var count = await Method1();
        Method3(count);
    }

    public static async Task<int> Method1()
    {
        int count = 0;
        await Task.Run(() =>
        {
            for (int i = 0; i < 100; i++)
            {
                Console.WriteLine(" Method 1");
                count += 1;
            }
        });
        return count;
    }

    public static void Method2()
    {
        for (int i = 0; i < 25; i++)
        {
            Console.WriteLine(" Method 2");
        }
    }

    public static void Method3(int count)
    {
        Console.WriteLine("Total count is " + count);
    }
}

출력은 다음과 같습니다.

Method 2
Method 2
Method 2
Method 2
Method 2
Method 2
Method 2
Method 2
Method 2
Method 2
Method 1
Method 1
Method 1
Method 1
Method 1
Method 1
Method 1
Method 1
Method 1
Method 1
Total count is 10

위 구문은 Main 에 async 가 붙어 있습니다.
Main 에 async 가 붙어 비동기함수로 될 경우 Main 도 await 이후부터는 비동기로 동작합니다.
주 스레드는 event.wait 상태로 되고 작업자 스레드에서 main에 수행되게 됩니다.
Method3에는 Method1의 반환 형식인 하나의 매개 변수가 필요합니다. 여기서 await 키워드는 Method1 작업이 완료될 때까지 기다리는 데 매우 중요합니다.

비동기 프로그래밍의 실제 활용

비동기 프로그래밍은 .NET Framework 4.5 및 Windows 런타임에서 지원하는 일부 API에서 지원됩니다. 이를 통해 효율적인 비동기 작업을 수행할 수 있습니다.

아래는 큰 텍스트 파일에서 비동기적으로 문자를 읽고 그 길이를 얻는 예시입니다.

class Program
{
    static void Main()
    {
        Task task = new Task(CallMethod);
        task.Start();
        task.Wait();
        Console.ReadLine();
    }

    static async void CallMethod()
    {
        string filePath = "D:\\samplefile.txt";
        Task<int> task = ReadFile(filePath);

        Console.WriteLine(" Other Work 1");
        Console.WriteLine(" Other Work 2");
        Console.WriteLine(" Other Work 3");

        int length = await task;
        Console.WriteLine(" Total length: " + length);

        Console.WriteLine(" After work 1");
        Console.WriteLine(" After work 2");
    }

    static async Task<int> ReadFile(string file)
    {
        int length = 0;

        Console.WriteLine(" File reading is starting");
        using (StreamReader reader = new StreamReader(file))
        {
            // Reads all characters from the current position to the end of the stream asynchronously
            // and returns them as one string.
            string s = await reader.ReadToEndAsync();

            length = s.Length;
        }
        Console.WriteLine(" File reading is completed");
        return length;
    }
}
 File reading is starting
 Other Work 1
 Other Work 2
 Other Work 3
 File reading is completed
 Total length: 51923055
 After work 1
 After work 2

위에 제공된 코드에서 ReadFile 메서드를 호출하여 텍스트 파일의 내용을 읽고 텍스트 파일에 있는 총 문자의 길이를 가져옵니다.

우리 sampleText.txt에서는 파일에 너무 많은 문자가 포함되어 있으므로 모든 문자를 읽는 데 시간이 오래 걸립니다.

여기서는 비동기 프로그래밍을 사용하여 파일의 모든 내용을 읽으므로 이 메서드에서 반환 값을 가져오고 다른 코드 줄을 실행할 때까지 기다리지 않습니다. 그러나 await 키워드를 사용하고 있기 때문에 아래에 주어진 코드 줄을 기다려야 하며 아래 코드 줄에 대한 반환 값을 사용합니다.

int length = await task;
Console.WriteLine(" Total length: " + length);

그런 다음 다른 코드 줄이 순차적으로 실행됩니다.

Console.WriteLine(" After work 1");
Console.WriteLine(" After work 2");

결론

이러한 예시를 통해 C#에서의 비동기 프로그래밍을 더 잘 이해할 수 있습니다. Async와 Await 키워드는 비동기 작업을 보다 쉽게 처리할 수 있도록 도와줍니다.

Leave a Comment