Преобразование цикла в задачи

У меня есть следующий синхронный код:

foreach ( var step in result ) {
  step.Run();
}

Я попытался преобразовать его в задачи, но я этого не сделал. Я попытался преобразовать его с помощью Task.WhenAll, как это (и я добавил async к сигнатуре метода):

var tasks = new List<Task>();
foreach ( var step in result ) {
    tasks.Add( new Task( () => step.Run() ) );
}
await Task.WhenAll( tasks );

Это немедленно возвращается и не выполняет метод Run(). Затем я попытался преобразовать его в следующий код:

var tasks = new List<Task>();
foreach ( var step in result ) {
    tasks.Add( new Task( () => step.Run() ) );
}
var task = Task.WhenAll( tasks );
task.Wait();

Это блокирует навсегда. Однако, когда я создаю внутри цикла, он работает:

foreach ( var step in result ) {
    var t = Task.Run( () => step.Run() );
    t.Wait();
}

Если я использую вместо этого await Task.Run( () => step.Run() );, он ждет только первый и возобновит основной поток.

Метод run выглядит следующим образом:

public async void Run() {
    var result = Work();
    if ( null != result && result.Count > 0 ) {
        var tasks = new List<Task>();
        foreach ( var step in result ) {
            await Task.Run( () => step.Run() );
        }
    }
}

Все шаги реализуют метод Work() (который является абстрактным в базовом классе). Мой первый шаг выглядит следующим образом:

class NoWorkStep : WorkerStep {
    protected override IList<WorkerStep> Work() {
        Console.WriteLine( "HERE" );
        List<WorkerStep> newList = new List<WorkerStep>();
        for ( int i = 0; i < 10; i++ ) {
            newList.Add( new NoWorkStep2() );
        }
        return newList;
    }
}

И мой второй шаг выглядит так:

class NoWorkStep2 : WorkerStep {
    protected override IList<WorkerStep> Work() {
        Console.WriteLine( "HERE-2" );
        return new List<WorkerStep>();
    }
}

Я просто создаю экземпляр NoWorkStep и звоню instance.Run().

Где у меня проблема с выполнением шагов с помощью Task.WhenAll?

Изменить: Вызов кода после того, как я изменил метод Run на async Task RunAsync:

private static async void doIt() {
  var step = new NoWorkStep();
  await step.RunAsync();
}

Ответ 1

Позволяет отобразить проблемы с вашим кодом:

new Task(() => step.Run())

Это возвращает холод Task, что означает, что Task фактически не запущен. Чтобы начать работу, вам нужно позвонить:

new Task(() => step.Run()).Start)

Но вы не должны использовать new Task в любом случае, вы должны использовать Task.Run.

Если вместо этого я буду использовать Task.Run(() = > step.Run()); он ждет только первый и возобновляет основной поток.

Это потому, что Run есть async void, которого нельзя ждать. async void может использоваться только в обработчиках событий верхнего уровня, где это явно не так.

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

public async Task RunAsync() 
{
    var result = Work();
    var stepTasks = result.Select(step => Task.Run(() => step.Run()));
    await Task.WhenAll(steps);
}

Это гарантирует выполнение всех задач после завершения выполнения RunAsync.

Ответ 2

Кажется, вы не начинаете задачи.

Try:

var tasks = new List<Task>();

foreach (var step in result) 
{
    var t = new Task(() => step.Run());
    t.Start();
    tasks.Add(t);
}

Task.WhenAll(tasks);

Ответ 3

Вы можете использовать Parallel.ForEach.

Parallel.ForEach(result, step => step.Run());

Таким образом, вы даже не обманываете себя с нижестоящими частями Parallel Framework.