Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
С помощью Task.WhenAnyможно одновременно запускать несколько задач и обрабатывать их по одному, а не обрабатывать их в том порядке, в котором они запущены.
В следующем примере используется запрос для создания коллекции задач. Каждая задача скачивает содержимое указанного веб-сайта. В каждой итерации цикла во время ожидания вызов WhenAny возвращает задачу в коллекции задач, завершающих загрузку первым. Эта задача удаляется из коллекции и обрабатывается. Цикл повторяется, пока коллекция не содержит больше задач.
Предпосылки
Вы можете следовать этому руководству, используя один из следующих вариантов:
- Visual Studio 2022 с установленным компонентом разработки десктопных приложений .NET. Пакет SDK для .NET устанавливается автоматически при выборе этой рабочей нагрузки.
- Пакет SDK для .NET с выбранным редактором кода, например Visual Studio Code.
Создание примера приложения
Создайте консольное приложение .NET Core. Ее можно создать с помощью команды dotnet new console или из 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"
};
Предоставляет HttpClient
возможность отправлять HTTP-запросы и получать 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
в следующем коде возвращает Task<TResult>целое число: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
вызовет первое дочернее исключение, хранящееся вAggregateException
свойстве, в отличие от чтения Task<TResult>.Result свойства, которое вызовет исключениеAggregateException
.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-адреса и длины в консоль.
Запустите программу несколько раз, чтобы убедиться, что скачанные длины не всегда отображаются в одном порядке.
Caution
Вы можете использовать WhenAny
цикл, как описано в примере, для решения проблем, связанных с небольшим количеством задач. Однако другие подходы более эффективны при наличии большого количества задач для обработки. Дополнительные сведения и примеры см. в разделе "Обработка задач по мере их выполнения".
Упрощение подхода Task.WhenEach
Циклwhile
, реализованный в методе, можно упростить с помощью нового Task.WhenEach метода, представленного в SumPageSizesAsync
.NET 9, вызвав его в await foreach
цикле.
Замените ранее реализованный 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.WhenAny
задачу вручную и удалять ее, так как Task.WhenEach
выполняет итерацию по задаче в порядке их завершения.
Полный пример
Следующий код представляет собой полный текст файла 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