다음을 통해 공유


완료할 때 비동기 작업 처리(C#)

이를 사용하면 Task.WhenAny여러 작업을 동시에 시작하고 작업이 시작된 순서대로 처리하지 않고 완료될 때 하나씩 처리할 수 있습니다.

다음 예제에서는 쿼리를 사용하여 작업 컬렉션을 만듭니다. 각 태스크는 지정된 웹 사이트의 콘텐츠를 다운로드합니다. while 루프의 각 반복에서 대기한 호출은 먼저 다운로드를 WhenAny 완료하는 태스크 컬렉션의 작업을 반환합니다. 해당 작업은 컬렉션에서 제거되고 처리됩니다. 루프는 컬렉션에 더 이상 태스크가 포함되지 않을 때까지 반복됩니다.

필수 조건

다음 옵션 중 하나를 사용하여 이 자습서를 따를 수 있습니다.

  • Visual Studio 2022에서 .NET 데스크톱 개발 워크로드가 설치됨. 이 워크로드를 선택하면 .NET SDK가 자동으로 설치됩니다.
  • Visual Studio Code와 같이 선택한 코드 편집기가 있는 .NET SDK입니다.

예제 애플리케이션 만들기

새 .NET Core 콘솔 애플리케이션을 만듭니다. dotnet 새 콘솔 명령을 사용하거나 Visual Studio에서 만들 수 있습니다.

코드 편집기에서 Program.cs 파일을 열고 기존 코드를 다음 코드로 바꿉다.

using System.Diagnostics;

namespace ProcessTasksAsTheyFinish;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
    }
}

필드 추가

클래스 정의에서 Program 다음 두 필드를 추가합니다.

static readonly HttpClient s_client = new HttpClient
{
    MaxResponseContentBufferSize = 1_000_000
};

static readonly IEnumerable<string> s_urlList = new string[]
{
    "https://learn.microsoft.com",
    "https://learn.microsoft.com/aspnet/core",
    "https://learn.microsoft.com/azure",
    "https://learn.microsoft.com/azure/devops",
    "https://learn.microsoft.com/dotnet",
    "https://learn.microsoft.com/dynamics365",
    "https://learn.microsoft.com/education",
    "https://learn.microsoft.com/enterprise-mobility-security",
    "https://learn.microsoft.com/gaming",
    "https://learn.microsoft.com/graph",
    "https://learn.microsoft.com/microsoft-365",
    "https://learn.microsoft.com/office",
    "https://learn.microsoft.com/powershell",
    "https://learn.microsoft.com/sql",
    "https://learn.microsoft.com/surface",
    "https://learn.microsoft.com/system-center",
    "https://learn.microsoft.com/visualstudio",
    "https://learn.microsoft.com/windows",
    "https://learn.microsoft.com/maui"
};

HTTP HttpClient 요청을 보내고 HTTP 응답을 수신하는 기능을 노출합니다. 애플리케이션 s_urlList 에서 처리할 모든 URL을 보유합니다.

애플리케이션 진입점 업데이트

콘솔 애플리케이션의 주요 진입점은 메서드입니다 Main . 기존 메서드를 다음으로 바꿉다.

static Task Main() => SumPageSizesAsync();

이제 업데이트 Main 된 메서드는 실행 파일에 비동기 진입점을 허용하는 비동기 메서드로 간주됩니다. 에 대한 호출로 표현됩니다 SumPageSizesAsync.

비동기 합계 페이지 크기 메서드 만들기

Main 메서드 아래에 메서드를 추가합니다.SumPageSizesAsync

static async Task SumPageSizesAsync()
{
    var stopwatch = Stopwatch.StartNew();

    IEnumerable<Task<int>> downloadTasksQuery =
        from url in s_urlList
        select ProcessUrlAsync(url, s_client);

    List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

    int total = 0;
    while (downloadTasks.Any())
    {
        Task<int> finishedTask = await Task.WhenAny(downloadTasks);
        downloadTasks.Remove(finishedTask);
        total += await finishedTask;
    }

    stopwatch.Stop();

    Console.WriteLine($"\nTotal bytes returned:  {total:#,#}");
    Console.WriteLine($"Elapsed time:          {stopwatch.Elapsed}\n");
}

루프는 while 각 반복에서 작업 중 하나를 제거합니다. 모든 작업이 완료되면 루프가 종료됩니다. 메서드는 인스턴스화 하 고 시작 하 여 시작 합니다 Stopwatch. 그런 다음, 실행할 때 태스크 컬렉션을 만드는 쿼리를 포함합니다. 다음 코드의 각 호출 ProcessUrlAsync 은 정수 TResult 인 다음을 반환Task<TResult>합니다.

IEnumerable<Task<int>> downloadTasksQuery =
    from url in s_urlList
    select ProcessUrlAsync(url, s_client);

LINQ를 사용한 지연 실행 으로 인해 각 작업을 시작하도록 호출 Enumerable.ToList 합니다.

List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

루프는 while 컬렉션의 각 작업에 대해 다음 단계를 수행합니다.

  1. 다운로드를 WhenAny 완료한 컬렉션의 첫 번째 작업을 식별하기 위한 호출을 기다립니다.

    Task<int> finishedTask = await Task.WhenAny(downloadTasks);
    
  2. 컬렉션에서 해당 작업을 제거합니다.

    downloadTasks.Remove(finishedTask);
    
  3. Awaits finishedTask- 호출에 ProcessUrlAsync의해 반환됩니다. finishedTask 변수는 Task<TResult> 정수인 위치 TResult 입니다. 작업이 이미 완료되었지만 다음 예제와 같이 다운로드한 웹 사이트의 길이를 검색하기 위해 대기합니다. 태스크에 오류가 await 있는 경우 속성을 읽 Task<TResult>.Result 는 것과 달리 첫 번째 자식 예외가 throwAggregateException되고 이 예외가 throwAggregateException됩니다.

    total += await finishedTask;
    

프로세스 메서드 추가

메서드 아래에 다음 ProcessUrlAsync 메서드를 SumPageSizesAsync 추가합니다.

static async Task<int> ProcessUrlAsync(string url, HttpClient client)
{
    byte[] content = await client.GetByteArrayAsync(url);
    Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

    return content.Length;
}

지정된 URL의 경우 메서드는 제공된 인스턴스를 client 사용하여 응답을 로 byte[]가져옵니다. URL 및 길이가 콘솔에 기록된 후에 길이가 반환됩니다.

프로그램을 여러 번 실행하여 다운로드한 길이가 항상 같은 순서로 표시되지 않는지 확인합니다.

주의

예제에 설명된 대로 루프에서 사용하면 WhenAny 적은 수의 작업이 포함된 문제를 해결할 수 있습니다. 그러나 처리할 작업이 많은 경우 다른 방법이 더 효율적입니다. 자세한 내용 및 예제는 완료되는 작업 처리를 참조하세요.

를 사용하여 접근 방식 간소화 Task.WhenEach

메서드에서 구현된 루프는 while 루프에서 SumPageSizesAsync 호출 await foreach 하여 .NET 9에 도입된 새 Task.WhenEach 메서드를 사용하여 간소화할 수 있습니다.
이전에 구현된 while 루프를 바꿉다.

    while (downloadTasks.Any())
    {
        Task<int> finishedTask = await Task.WhenAny(downloadTasks);
        downloadTasks.Remove(finishedTask);
        total += await finishedTask;
    }

간소화된 다음을 사용하여 다음을 수행합니다 await foreach.

    await foreach (Task<int> t in Task.WhenEach(downloadTasks))
    {
        total += await t;
    }

이 새로운 접근 방식을 사용하면 작업을 완료 순서대로 반복하기 때문에 Task.WhenEach 작업을 수동으로 호출하고 완료되는 작업을 제거하도록 더 이상 반복적으로 호출 Task.WhenAny 없습니다.

전체 예제

다음 코드는 예제에 대한 Program.cs 파일의 전체 텍스트입니다.

using System.Diagnostics;

HttpClient s_client = new()
{
    MaxResponseContentBufferSize = 1_000_000
};

IEnumerable<string> s_urlList = new string[]
{
    "https://learn.microsoft.com",
    "https://learn.microsoft.com/aspnet/core",
    "https://learn.microsoft.com/azure",
    "https://learn.microsoft.com/azure/devops",
    "https://learn.microsoft.com/dotnet",
    "https://learn.microsoft.com/dynamics365",
    "https://learn.microsoft.com/education",
    "https://learn.microsoft.com/enterprise-mobility-security",
    "https://learn.microsoft.com/gaming",
    "https://learn.microsoft.com/graph",
    "https://learn.microsoft.com/microsoft-365",
    "https://learn.microsoft.com/office",
    "https://learn.microsoft.com/powershell",
    "https://learn.microsoft.com/sql",
    "https://learn.microsoft.com/surface",
    "https://learn.microsoft.com/system-center",
    "https://learn.microsoft.com/visualstudio",
    "https://learn.microsoft.com/windows",
    "https://learn.microsoft.com/maui"
};

await SumPageSizesAsync();

async Task SumPageSizesAsync()
{
    var stopwatch = Stopwatch.StartNew();

    IEnumerable<Task<int>> downloadTasksQuery =
        from url in s_urlList
        select ProcessUrlAsync(url, s_client);

    List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

    int total = 0;
    while (downloadTasks.Any())
    {
        Task<int> finishedTask = await Task.WhenAny(downloadTasks);
        downloadTasks.Remove(finishedTask);
        total += await finishedTask;
    }

    stopwatch.Stop();

    Console.WriteLine($"\nTotal bytes returned:    {total:#,#}");
    Console.WriteLine($"Elapsed time:              {stopwatch.Elapsed}\n");
}

static async Task<int> ProcessUrlAsync(string url, HttpClient client)
{
    byte[] content = await client.GetByteArrayAsync(url);
    Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

    return content.Length;
}

// Example output:
// https://learn.microsoft.com                                      132,517
// https://learn.microsoft.com/powershell                            57,375
// https://learn.microsoft.com/gaming                                33,549
// https://learn.microsoft.com/aspnet/core                           88,714
// https://learn.microsoft.com/surface                               39,840
// https://learn.microsoft.com/enterprise-mobility-security          30,903
// https://learn.microsoft.com/microsoft-365                         67,867
// https://learn.microsoft.com/windows                               26,816
// https://learn.microsoft.com/maui                               57,958
// https://learn.microsoft.com/dotnet                                78,706
// https://learn.microsoft.com/graph                                 48,277
// https://learn.microsoft.com/dynamics365                           49,042
// https://learn.microsoft.com/office                                67,867
// https://learn.microsoft.com/system-center                         42,887
// https://learn.microsoft.com/education                             38,636
// https://learn.microsoft.com/azure                                421,663
// https://learn.microsoft.com/visualstudio                          30,925
// https://learn.microsoft.com/sql                                   54,608
// https://learn.microsoft.com/azure/devops                          86,034

// Total bytes returned:    1,454,184
// Elapsed time:            00:00:01.1290403

참고하십시오