C#교과서 마스터하기 44. 비동기 프로그래밍(async&await)

1. 비동기 프로그래밍(async&await)

https://youtu.be/PwoF8jsCUtM

01. 동기(Synchronous)와 비동기(Asynchronous)

  • 동기
var data = DownloadData(...);
ProcessData(data);

  • 비동기
var future = DownloadDataAsync(...);
future.ContinewWith(data => ProcessData(data));

  • 저녁식사 모델링 : 동기 프로그래밍

  • 저녁식사 모델링 : 비동기 프로그래밍(동기 포함)

  • 저녁식사 모델링 : 비동기 프로그래밍

  • 저녁식사 모델링

2. 프로젝트

  • Cooking.cs
    • async Task<>
      • 비동기 선언 Task of T로 반환
    • await Task.Delay()
      • Thread.Sleep();의 비동기 버전
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Dinner.Common
{
    public class Cooking
    {
        // 동기 방식의 밥 만들기 메서드 
        // <returns></returns>
        public Rice MakeRice()
        {
            Console.WriteLine("밥 생성중...");
            Thread.Sleep(3001);
            return new Rice();
        }

        // 비동기 방식의 밥 만들기 메서드 
        // <returns></returns>
        public async Task<Rice> MakeRiceAsync()
        {
            Console.WriteLine("밥 생성중...");
            await Task.Delay(3001); //[A]
            return new Rice();
        }

        // 동기 방식의 국 만들기 메서드
        // <returns></returns>
        public Soup MakeSoup()
        {
            Console.WriteLine("국 생성중...");
            Thread.Sleep(3001);
            return new Soup();
        }

        // 비동기 방식의 국 만들기 메서드
        // <returns></returns>
        public async Task<Soup> MakeSoupAsync()
        {
            Console.WriteLine("국 생성중...");
            await Task.Run(() => Task.Delay(3001)); //[B]
            return new Soup();
        }

        // 동기 방식의 달걀 만들기 메서드
        // <returns>달걀</returns>
        public Egg MakeEgg()
        {
            Console.WriteLine("달걀 생성중...");
            Thread.Sleep(3001);
            return new Egg();
        }

        // 비동기 방식의 달걀 만들기 메서드
        // <returns>달걀</returns>
        public async Task<Egg> MakeEggAsync()
        {
            Console.WriteLine("달걀 생성중...");
            await Task.Delay(TimeSpan.FromMilliseconds(3001));
            return await Task.FromResult<Egg>(new Egg()); //[C]
        }
    }

    public class Rice
    {
        // Pass
    }

    public class Soup
    {
        // Pass
    }

    public class Egg
    {
        // Pass
    }
}
  • Sync / Program.cs
using Dinner.Common;
using System;
using System.Diagnostics;

namespace Dinner.Sync
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            //[1] 밥 만들기
            Rice rice = (new Cooking()).MakeRice(); // 스레드 차단: true
            Console.WriteLine($"밥 준비 완료 - {rice.GetHashCode()}");

            //[2] 국 만들기
            Soup soup = (new Cooking()).MakeSoup();
            Console.WriteLine($"국 준비 완료 - {soup.GetHashCode()}");

            //[3] 달걀 만들기
            Egg egg = (new Cooking()).MakeEgg();
            Console.WriteLine($"달걀 준비 완료 - {egg.GetHashCode()}");

            stopwatch.Stop();

            Console.WriteLine($"\n시간: {stopwatch.ElapsedMilliseconds}밀리초");
            Console.WriteLine("동기 방식으로 식사 준비 완료");
        }
    }
}

  • Async / Program.cs
    • 각 메서드가 await로 해두었기 때문에 동기와 시간은 같지만 행동은비동기 방식이다
using Dinner.Common;
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Dinner.Async
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            Rice rice = await (new Cooking()).MakeRiceAsync(); // 스레드 차단: false
            Console.WriteLine($"밥 준비 완료: {rice.GetHashCode()}");

            Soup soup = await (new Cooking()).MakeSoupAsync();
            Console.WriteLine($"국 준비 완료: {soup.GetHashCode()}");

            Egg egg = await (new Cooking()).MakeEggAsync();
            Console.WriteLine($"달걀 준비 완료: {egg.GetHashCode()}");

            stopwatch.Stop();

            Console.WriteLine($"\n시간: {stopwatch.ElapsedMilliseconds}밀리초");
            Console.WriteLine("비동기 방식으로 식사 준비 완료");
        }
    }
}

  • Program.cs
    • await
    • Task<>
      • 3개의 Async 메서드가 동시 실행
    • await Task.WhenAll()
      • 모든 작업이 다 완료될 때까지 대기
    • allTasks.Any()
      • 작업이 하나라도 있으면 실행
using System;
using static System.Console;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Dinner.Common;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace Dinner
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("[?] 어떤 방식으로 실행할까요? (0~4 번호 입력)\n" +
                "0. 동기\t\t1. await\t2. Task<T>\t3. WhenAll\t4. WhenAny ");
            var number = Convert.ToInt32(Console.ReadLine());

            switch (number)
            {
                case 1: // 비동기(동기 프로그램을 포함한)
                    {
                        Stopwatch stopwatch = new Stopwatch();
                        stopwatch.Start();

                        Egg egg = await (new Cooking()).MakeEggAsync();
                        Console.WriteLine($"달걀 재료 준비 완료: {egg.GetHashCode()}");

                        Rice rice = await (new Cooking()).MakeRiceAsync();
                        Console.WriteLine($"김밥 준비 완료: {rice.GetHashCode()}");

                        Soup soup = await (new Cooking()).MakeSoupAsync();
                        Console.WriteLine($"국 준비 완료: {soup.GetHashCode()}");

                        stopwatch.Stop();

                        Console.WriteLine($"\n시간: {stopwatch.ElapsedMilliseconds}");
                        Console.WriteLine("비동기 방식으로 식사(김밥) 준비 완료");
                    }
                    break;
                case 2: // 비동기(함께 실행)
                    {
                        Stopwatch stopwatch = new Stopwatch();
                        stopwatch.Start();

                        // 3개의 Async 메서드가 동시 실행
                        Task<Rice> riceTask = (new Cooking()).MakeRiceAsync();
                        Task<Soup> soupTask = (new Cooking()).MakeSoupAsync();
                        Task<Egg> eggTask = (new Cooking()).MakeEggAsync();

                        Rice rice = await riceTask;
                        Console.WriteLine($"식탁에 밥 준비 완료: {rice.GetHashCode()}");
                        Soup soup = await soupTask;
                        Console.WriteLine($"식탁에 국 준비 완료: {soup.GetHashCode()}");
                        Egg egg = await eggTask;
                        Console.WriteLine($"식탁에 달걀 준비 완료: {egg.GetHashCode()}");

                        stopwatch.Stop();

                        Console.WriteLine($"\n시간: {stopwatch.ElapsedMilliseconds}");
                        Console.WriteLine("비동기 방식으로 식사 준비 완료");
                    }
                    break;
                case 3: // 비동기(모두 완료되는 시점)
                    {
                        Stopwatch stopwatch = new Stopwatch();
                        stopwatch.Start();

                        // 3개의 Async 메서드가 동시 실행
                        Task<Rice> riceTask = (new Cooking()).MakeRiceAsync();
                        Task<Soup> soupTask = (new Cooking()).MakeSoupAsync();
                        Task<Egg> eggTask = (new Cooking()).MakeEggAsync();

                        // 모든 작업이 다 완료될 때까지 대기
                        await Task.WhenAll(riceTask, soupTask, eggTask);

                        Console.WriteLine("식탁에 모든 식사 준비 완료");

                        stopwatch.Stop();

                        Console.WriteLine($"\n시간: {stopwatch.ElapsedMilliseconds}");
                        Console.WriteLine("비동기 방식으로 식사 준비 완료");
                    }
                    break;
                case 4:
                    {
                        Stopwatch stopwatch = new Stopwatch();
                        stopwatch.Start();

                        // 3개의 Async 메서드가 동시 실행
                        Task<Rice> rTask = (new Cooking()).MakeRiceAsync();
                        Task<Soup> sTask = (new Cooking()).MakeSoupAsync();
                        Task<Egg> eTask = (new Cooking()).MakeEggAsync();

                        // 하나라도 작업이 끝나면 확인
                        var allTasks = new List<Task> { rTask, sTask, eTask };
                        while (allTasks.Any()) // 작업이 하나라도 있으면 실행
                        {
                            Task finished = await Task.WhenAny(allTasks);
                            if (finished == rTask)
                            {
                                Rice rice = await rTask;
                                Console.WriteLine($"밥 준비 완료 - {rice}");
                            }
                            else if (finished == sTask)
                            {
                                Soup soup = await sTask;
                                Console.WriteLine($"국 준비 완료 - {soup}");
                            }
                            else
                            {
                                Egg egg = await eTask;
                                Console.WriteLine($"달걀 준비 완료 - {egg}");
                            }
                            allTasks.Remove(finished); // 끝난 작업은 리스트에서 제거
                        }

                        stopwatch.Stop();

                        Console.WriteLine(
                            $"\n시간: {stopwatch.ElapsedMilliseconds}");
                        Console.WriteLine("비동기 방식으로 식사 준비 완료");
                    }
                    break;
                default: // 동기(Sync)
                    {
                        Stopwatch stopwatch = new Stopwatch();
                        stopwatch.Start();

                        //[1] 밥 만들기
                        Rice rice = (new Cooking()).MakeRice(); // 스레드 차단: true
                        Console.WriteLine($"밥 준비 완료 - {rice.GetHashCode()}");

                        //[2] 국 만들기
                        Soup soup = (new Cooking()).MakeSoup();
                        Console.WriteLine($"국 준비 완료 - {soup.GetHashCode()}");

                        //[3] 달걀 만들기
                        Egg egg = (new Cooking()).MakeEgg();
                        Console.WriteLine($"달걀 준비 완료 - {egg.GetHashCode()}");

                        stopwatch.Stop();

                        Console.WriteLine($"\n시간: {stopwatch.ElapsedMilliseconds}");
                        Console.WriteLine("동기 방식으로 식사 준비 완료");
                    }
                    break;
            }
        }
    }
}

좋은 웹페이지 즐겨찾기