C# 여러 작업 대기

Naila Saad Siddiqui 2024년2월15일
  1. C#의 동기 및 비동기 프로그래밍
  2. C#에서 async 메서드 및 await 여러 작업 구현
C# 여러 작업 대기

이 간단한 가이드는 비동기 프로그래밍 모델에 대해 설명하고 async, awaitTask와 같은 관련 키워드를 설명합니다. 특히 C#을 사용하여 여러 작업을 기다리는 개념을 보여줍니다.

실제 주제로 이동하기 전에 동기식 프로그래밍 모델과 비동기식 프로그래밍 모델의 차이점을 간략히 살펴봐야 합니다. 자, 시작하겠습니다!

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

동기식 및 비동기식 모델은 컴퓨터 프로그래밍에서 매우 중요합니다. 용어는 각 프로그래밍 모델의 기능과 차이점에 대한 정보를 제공합니다.

동기 작업은 일반적으로 차례로 순차적으로 수행됩니다. 비동기 작업은 순서에 상관없이 동시에 수행할 수 있습니다.

C#의 동기식 프로그래밍

동기식은 리액티브 프로그래밍 시스템에 가장 적합한 차단 아키텍처입니다. 단일 스레드 모델로서 엄격한 시퀀스 집합을 고수하므로 각 작업이 한 번에 하나씩 정확하게 연속적으로 수행됩니다.

하나의 프로세스가 수행되는 동안 추가 절차에 대한 지침이 차단됩니다. 하나의 작업을 완료하면 다음 작업이 시작됩니다.

동기식 프로그래밍이 작동하는 방식을 이해하려면 워키토키 예제를 고려하십시오.

한 사람은 전화 통화 중에 말하고 다른 사람은 듣습니다. 두 번째 사람은 일반적으로 첫 번째 사람이 말을 마친 직후에 응답합니다.

프로그래밍 측면에서 동기식 프로그래밍을 이해하기 위해 다음 예제를 고려하십시오.

using System;
using System.Threading;
public class Program {
  public static void Main(string[] args) {
    Process1();
    Process2();
  }
  static void Process1() {
    Console.WriteLine("Process 1 started");
    // You can write some code here that can take a longer time
    Thread.Sleep(5000);  // holing on execution for 5 sec
    Console.WriteLine("Process 1 Completed");
  }
  static void Process2() {
    Console.WriteLine("Process 2 Started");
    // write some code here that takes relatively less time
    Console.WriteLine("Process 2 Completed");
  }
}

위 예시의 Process1() 메서드는 서버에서 파일을 읽어오거나, 많은 데이터를 반환하는 웹 API에 접속하거나, 대용량 파일을 업로드하거나 다운로드하는 등 시간이 많이 걸리는 프로세스를 수행하는 데 사용됩니다.

실행하는 데 시간이 조금 더 걸립니다(설명을 위해 Thread.Sleep(5000)이 5초 동안 유지함). process2() 메소드는 Process1() 메소드를 따르며 동시에 시작되지 않습니다.

위의 프로그램은 동기식으로 실행됩니다. 이는 실행이 Main() 함수로 시작되고 그 후에 Process1() 메서드와 Process2() 메서드가 각각 순서대로 수행됨을 나타냅니다(즉, 엄밀히 말하면 하나씩 차례로 수행됨).

애플리케이션은 실행 중에 중지되고 응답하지 않습니다(아래 출력 화면 참조). 동기식 프로그래밍은 이전 줄이 실행을 마칠 때까지 다음 줄로 진행하기 위해 대기합니다.

동기식 프로그래밍을 완전히 이해하기 위해 출력을 살펴보겠습니다.

동기 프로그래밍 출력

위의 출력에서 Process1이 완료되기를 기다리는 동안 실행이 차단되었음을 알 수 있습니다. 실행이 완료되는 즉시 Process2가 실행됩니다.

C#의 비동기 프로그래밍

반면에 비동기 프로그래밍은 네트워킹 및 통신에서 가장 잘 작동하는 다중 스레드 스타일입니다. 비차단 설계로서 비동기 아키텍처는 하나 이상의 작업이 실행되는 동안 후속 실행을 방지하지 않습니다.

여러 관련 작업은 다른 작업이 완료될 때까지 기다리지 않고 비동기식 프로그래밍으로 동시에 실행할 수 있습니다. 메시지를 받은 후 즉시 회신하는 대신 비동기식 통신에 종사하는 사람들은 메시지를 읽고 처리하기 전에 편리하거나 실현 가능할 때까지 기다립니다.

비동기 통신 기술에는 문자 메시지가 포함됩니다. 한 사람이 문자 메시지를 보낼 수 있고 받는 사람은 언제든지 답장을 보낼 수 있습니다. 응답을 기다리는 동안 보낸 사람은 다른 작업을 수행할 수 있습니다.

위에서 설명한 예에 따르면 예를 들어 Process1() 메서드는 비동기 프로그래밍 모델의 스레드 풀과 다른 스레드에서 실행됩니다. 반대로 기본 응용 프로그램 스레드는 후속 명령문을 계속 실행합니다.

Microsoft는 asyncawait 키워드와 Task 또는 Task<TResult> 클래스를 사용하여 .NET Framework 또는 .NET Core 애플리케이션에서 비동기 프로그래밍을 구현할 때 작업 기반 비동기 패턴을 사용할 것을 제안합니다.

비동기 프로그래밍을 사용하여 이전 코드를 다시 작성해 보겠습니다.

using System;
using System.Threading.Tasks;

public class Program {
  public static async Task Main(string[] args) {
    Process1();
    Process2();
    Console.ReadKey();
  }

  public static async void Process1() {
    Console.WriteLine("Process 1 Started");
    await Task.Delay(5000);  // holding execution for 5 seconds
    Console.WriteLine("Process 1 Completed");
  }
  static void Process2() {
    Console.WriteLine("Process 2 Started");
    // Write code here
    Console.WriteLine("Process 2 Completed");
  }
}

async 키워드는 위의 예제에서 Main() 메서드를 식별하는 데 사용되며 Task는 반환 유형입니다.

메소드는 async 키워드에 의해 비동기식으로 식별됩니다. 메서드 체인의 모든 메서드는 비동기 프로그래밍을 수행하기 위해 비동기여야 합니다.

따라서 자식 메서드를 비동기식으로 만들려면 Main() 함수가 async여야 합니다.

async 키워드는 Process1() 메서드를 비동기식으로 표시하고 Task.Delay(5000);를 기다립니다. 스레드가 5초 동안 유용한 실행을 방지합니다.

메인 애플리케이션 스레드의 async Main() 메서드는 이제 프로그램 실행을 시작합니다. 메인 애플리케이션 스레드는 async Process1()이 완료될 때까지 기다리지 않고 Process2() 메서드를 사용하는 후속 명령문을 계속 실행합니다.

이 코드 세그먼트의 출력은 다음과 같습니다.

비동기 프로그래밍 출력

async, awaitTask 키워드 사용

async 메서드가 호출 코드에 값을 반환하는 경우 awaitTask와 함께 async를 사용합니다. 위의 예에서 async 키워드를 사용하여 기본 비동기식 void 메서드를 사용하는 방법을 보여주었습니다.

async 메서드가 값을 반환하기 전에 await 키워드가 값을 기다립니다. 결과적으로 반환 값을 받을 때까지 주 응용 프로그램 스레드는 거기에서 중단됩니다.

Task 클래스는 비동기 작업을 나타내며 결과를 반환할 수 있는 프로세스는 일반 형식 Task<TResult>로 표시됩니다. 위의 경우 await Task가 사용되었습니다. awaitDelay(5000)가 5초 동안 대기하는 async 작업을 시작한 후 5초 동안 스레드를 유지합니다.

async 메서드는 아래 예제에서 값을 반환합니다.

C#에서 async 메서드 및 await 여러 작업 구현

두 개의 프로세스가 있고 각각 값을 반환하고 해당 값이 이 둘의 합을 계산하는 다른 함수로 전달되는 예를 고려하십시오(합계는 예시 목적으로 계산되며 결과로 모든 작업을 수행할 수 있음).

이를 위해 프로세스가 완료되고 값을 반환할 때까지 기다린 다음 반환된 값을 다른 함수에 전달해야 합니다. 따라서 WhenAll 방법이 사용됩니다.

WhenAll 메서드는 모든 프로세스가 완료될 때까지 기다린 다음 반환된 결과를 저장합니다. 이는 아래 예에서 설명됩니다.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class Program {
  static async Task Main(string[] args) {
    Task<int> res1 = Process1();
    Task<int> res2 = Process2();

    Console.WriteLine("After two processes.");
    var ls = await Task.WhenAll(res1, res2);
    DoSomeTask(ls);

    Console.ReadKey();
  }

  static async Task<int> Process1() {
    Console.WriteLine("Process 1 Started");
    await Task.Delay(5000);  // hold execution for 5 seconds
    Console.WriteLine("Process 1 Completed");
    return 15;
  }

  static async Task<int> Process2() {
    Console.WriteLine("Process 2 Started");
    await Task.Delay(3000);  // hold execution for 3 seconds
    Console.WriteLine("Process 2 Completed");
    return 25;
  }

  static void DoSomeTask(int[] l) {
    int sum = 0;
    foreach (int i in l) sum += i;
    Console.WriteLine("sum of the list is: {0} ", sum);
  }
}

위의 코드 세그먼트에서 Process1Process2라는 두 개의 함수를 만들었습니다. 이 두 기능 모두 5초와 3초의 지연이 있습니다.

두 프로세스가 완료되면 반환된 값을 DoSomeTask,에 전달하고 이 숫자의 합을 계산하여 화면에 표시합니다. 이 코드 세그먼트의 출력은 다음과 같습니다.

반환 값을 사용한 비동기 프로그래밍