이를 사용하면 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 컬렉션의 각 작업에 대해 다음 단계를 수행합니다.
다운로드를
WhenAny완료한 컬렉션의 첫 번째 작업을 식별하기 위한 호출을 기다립니다.Task<int> finishedTask = await Task.WhenAny(downloadTasks);컬렉션에서 해당 작업을 제거합니다.
downloadTasks.Remove(finishedTask);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
참고하십시오
.NET