Ключом к решению этой проблемы является то, что троттлер может запускать задачи, а не запускать их заранее. И поскольку запуск задач явно со старым методом Task.Start
очень ограничен (предшествует и не может использовать механизм асинхронного ожидания), единственная альтернатива - позволить троттлеру создавать задачи. Есть несколько способов сделать это:
1) Пропускать фабрики задач вместо задач. Этот метод уже был продемонстрирован в других ответах.
private static async Task<TResult[]> RunAsyncThrottled<TResult>(
IEnumerable<Func<Task<TResult>>> taskFactories,
int maxDegreeOfParallelism)
{
//...
foreach (var taskFactory in taskFactories)
//...
var task = taskFactory();
TResult result = await task;
}
2) Передать последовательность элементов и одну фабрику задач, которая принимает элемент в качестве параметра. Это наиболее часто используемый метод:
private static async Task<TResult[]> RunAsyncThrottled<TSource, TResult>(
IEnumerable<TSource> items, Func<TSource, Task<TResult>> taskFactory,
int maxDegreeOfParallelism)
{
//...
foreach (var item in items)
//...
var task = taskFactory(item);
TResult result = await task;
}
3) Передача отложенного множества задач. Такое перечислимое можно создать с помощью LINQ или итераторов (методы, которые yield
). Здесь является полным примером.
private static async Task<TResult[]> RunAsyncThrottled<TResult>(
IEnumerable<Task<TResult>> tasks, int maxDegreeOfParallelism)
{
if (tasks is ICollection<Task<TResult>>) throw new ArgumentException(
"The enumerable should not be materialized.", nameof(tasks));
//...
foreach (var task in tasks)
//...
TResult result = await task;
}
Поскольку C # 8 теперь освобождено, существует альтернатива возвращаемому значению метода. Вместо возврата Task<TResult[]>
он может вернуть IAsyncEnumerable<TResult>
, что позволяет выполнять асинхронное перечисление с await foreach
.
private static async IAsyncEnumerable<TResult> RunAsyncThrottled<TSource, TResult>(
IEnumerable<TSource> items, Func<TSource, Task<TResult>> taskFactory,
int maxDegreeOfParallelism)
{
//...
foreach (var item in items)
//...
yield return await taskFactory(item);
}