Правильный способ использования Task.WaitAll /WhenAll с функциями пакетного выполнения и возврата результата

#c# #async-await #batch-processing

#c# #асинхронный-ожидание #пакетная обработка

Вопрос:

Мне нужно разделить группу NET.Ядро 3.1 HttpClient (Assembly System.Net.Http, версия = 4.2.2.0) веб-запросы в пакетах по 10, дождитесь завершения этих 10 вызовов, сохраните результат где-нибудь и повторяйте, пока все пакеты не будут завершены.

Пример: у меня есть конечная точка, которая получает идентификатор и возвращает некоторый json взамен. У меня есть список с 25 идентификаторами пользователей, мне нужно разделить работу на 10 веб-запросов одновременно синхронно, до завершения, получения результата и перехода к следующему пакету (итак, 10, получаем результат, 10, получаем результат, последние 5, получаем результат)

Лучший способ пакетной обработки через Linq, который я нашел, — это использование этого кода для пакетной обработки в библиотеке MoreLINQ, поэтому в коде вы увидите «Пакетный» метод.

Я пришел к следующему:

 //Pretend that HttpClient is already configured
//Concurrent queue to collect results from webservice, result order it's not i
var resultsCollection = new ConcurrentQueue<string>();
string inventedWebCall = @"http://fantasywebapi.com/searchById?";
//list of ids
var ids = Enumerable.Range(1, 25);

//Action that calls webrequest and enqueues result in thread safe queue
Action<int> webRequest = async (id) => {
    var webCall = $"{inventedWebCall}{id}";
    var webResult = await httpClient.GetAsync(new Uri(webCall));
    if (webResult.IsSuccessStatusCode)
    {
        resultsCollection.Enqueue(await webResult.Content.ReadAsStringAsync())
    }
};

//Gets a list of lists divided by 10 (10, 10, 5)
var batches = ids.Batch(10, batch => batch);
foreach (var batch in batches)
{
    //Collects the result of concurrent webrequest calls


    //Contains the tasks to await concurrently
    var batchTasks = new Task[batch.Count()];
    for (var i = 0; i < batch.Count(); i  )
    {
        batchTasks[i] = new Task(() => webRequest(batch.ElementAt(i)));
    }

    foreach (var batchToExec in batchTasks)
    {
        batchToExec.Start();
    }
    Task.WaitAll(batchTasks);
}
 

Но, похоже, возникают некоторые проблемы с учетом того факта, что пакеты должны выполняться в разное время и заполнять результирующую очередь.

Кто-нибудь может помочь?

Комментарии:

1. Action<int> webRequest = async (id) => <== У вас есть async void право здесь.

2. @TheodorZoulias и?

3. Async void = проблемы, если только он не используется по прямому назначению, то есть для обеспечения возможности асинхронных обработчиков событий. К сожалению, язык C # позволяет очень легко создавать async void лямбды по ошибке, поэтому мы должны быть осторожны каждый раз, когда используем async ключевое слово с лямбдой.

4. @TheodorZoulias Хорошо, спасибо. Я займусь этим вопросом

Ответ №1:

Похоже Tasks , что вы создаете запуск другой задачи и не ждете их завершения.
После await httpClient.GetAsync достижения ваша общая задача будет завершена, если она не ожидает результата webRequest действия.
Измените webRequest тип на Func<int, Task> и ждите их.
Ваш пример кода будет выглядеть так:

 //Pretend that HttpClient is already configured
//Concurrent queue to collect results from webservice, result order it's not important
var resultsCollection = new ConcurrentQueue<string>();
string inventedWebCall = @"http://fantasywebapi.com/searchById?";
//list of ids
var ids = Enumerable.Range(1, 25);

//Action that calls webrequest and enqueues result in thread safe queue
Func<int, Task> webRequest = async (id) => {
    var webCall = $"{inventedWebCall}{id}";
    var webResult = await httpClient.GetAsync(new Uri(webCall));
    if (webResult.IsSuccessStatusCode)
    {
        resultsCollection.Enqueue(await webResult.Content.ReadAsStringAsync())
    }
};

//Gets a list of lists divided by 10 (10, 10, 5)
var batches = ids.Batch(10, batch => batch);
foreach (var batch in batches)
{
    //Contains the tasks to await concurrently
    var batchTasks = new Task[batch.Count()];
    for (var i = 0; i < batch.Count(); i  )
    {
        batchTasks[i] = webRequest(batch.ElementAt(i));
    }

    Task.WaitAll(batchTasks);
}