Подтвердить что ты не робот

Выполнение задач параллельно

Хорошо, поэтому в основном у меня есть множество задач (10), и я хочу запустить их все одновременно и дождаться их завершения. По завершении я хочу выполнить другие задачи. Я прочитал кучу ресурсов об этом, но я не могу понять это правильно для моего конкретного случая...

Вот что я сейчас (код был упрощен):

public async Task RunTasks(){
var tasks = new List<Task>
{
    new Task(async () => await DoWork()),
    //and so on with the other 9 similar tasks
}

Parallel.ForEach(tasks, task =>
{
    task.Start();
});

Task.WhenAll(tasks).ContinueWith(done=>{
    //Run the other tasks
});
}

//This function perform some I/O operations
public async Task DoWork(){
    var results = await GetDataFromDatabaseAsync();
    foreach(var result in results){
        await ReadFromNetwork(result.Url);
    }
}

Итак, моя проблема заключается в том, что, когда я жду завершения задач с вызовом WhenAll, он говорит мне, что все задачи завершены, хотя ни одна из них не завершена. Я попытался добавить Console.WriteLine в мой foreach, и когда я ввел задачу продолжения, данные продолжают поступать из моего предыдущего Task, которые на самом деле не закончены.

Что я здесь делаю неправильно?

4b9b3361

Ответ 1

Вы почти никогда не должны использовать конструктор Task напрямую. В вашем случае эта задача вызывает только текущую задачу, которую вы не можете ждать.

Вы можете просто вызвать DoWork и вернуть задачу, сохранить ее в списке и дождаться завершения всех задач. Значение:

tasks.Add(DoWork());
// ...
await Task.WhenAll(tasks);

Однако асинхронные методы выполняются синхронно до тех пор, пока не будет достигнуто первое ожидание по незавершенной задаче. Если вы беспокоитесь о том, что эта часть занимает слишком много времени, используйте Task.Run, чтобы выгрузить ее в другой поток ThreadPool, а затем сохраните , задачу в списке:

tasks.Add(Task.Run(() => DoWork()));
// ...
await Task.WhenAll(tasks);

Ответ 2

По сути, вы смешиваете две несовместимые асинхронные парадигмы; т.е. Parallel.ForEach() и async-await.

Для чего вы хотите, сделайте то или другое. Например. вы можете просто использовать Parallel.For[Each]() и вообще отказаться от асинхронного ожидания. Parallel.For[Each]() будет возвращаться только после завершения всех параллельных задач, а затем вы можете перейти к другим задачам.

В коде есть и другие проблемы:

  • вы отмечаете метод async, но не ожидаете в нем (ожидание, которое у вас есть, есть в делетете, а не в методе);

  • вы почти наверняка хотите .ConfigureAwait(false) в ожидании, особенно если вы не пытаетесь сразу использовать результаты в потоке пользовательского интерфейса.

Ответ 3

Метод DoWork является асинхронным методом ввода-вывода. Это означает, что вам не нужно несколько потоков для выполнения нескольких из них, так как большую часть времени метод будет асинхронно ждать завершения ввода-вывода. Для этого достаточно одного потока.

public async Task RunTasks()
{
    var tasks = new List<Task>
    {
        DoWork(),
        //and so on with the other 9 similar tasks
    };

    await Task.WhenAll(tasks);

    //Run the other tasks            
}

Вы должны почти никогда не использовать конструктор Task для создания новой задачи. Чтобы создать асинхронную задачу ввода-вывода, просто вызовите метод async. Чтобы создать задачу, которая будет выполнена в потоке пула потоков, используйте Task.Run. Вы можете прочитать эту статью для подробного объяснения Task.Run и других вариантов создания задач.

Ответ 4

Если вы хотите запустить эту задачу параллельно в разных потоках с помощью TPL, вам может понадобиться что-то вроде этого:

public async Task RunTasks()
{
    var tasks = new List<Func<Task>>
    {
       DoWork,
       //...
    };

    await Task.WhenAll(tasks.AsParallel().Select(async task => await task()));

    //Run the other tasks
}

Этот подход распараллеливает только небольшое количество кода: очередь процесса в пул потоков и возврат незавершенного Task. Кроме того, для такого небольшого количества задач распараллеливание может занимать больше времени, чем просто работать асинхронно. Это может иметь смысл, только если ваши задачи выполняют более длительную (синхронную) работу до их первого ожидания.

В большинстве случаев лучшим способом будет:

public async Task RunTasks()
{
    await Task.WhenAll(new [] 
    {
        DoWork(),
        //...
    });
    //Run the other tasks
}

На мой взгляд, в вашем коде:

  • Вы не должны завершать свой код в Task перед тем, как перейти к Parallel.ForEach.

  • Вы можете просто await Task.WhenAll вместо использования ContinueWith.