Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting loop to tasks

I have the following synchronous code:

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

I tried to convert it to tasks but I failed to do so. I tried to convert it using Task.WhenAll like this (and I did append async to the method signature):

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

This returns immediately and doesn't execute the Run() method. Then I tried to convert it to the following code:

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

This blocks forever. However, when I create a within the loop it works:

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

If I use instead await Task.Run( () => step.Run() ); it awaits only the first one and resumes the main thread.

The run method looks like this:

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() );
        }
    }
}

All steps implement a Work() method (which is abstract in a base class). My first step looks like this:

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;
    }
}

And my second step looks like this:

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

I simple create an instance of NoWorkStep and call instance.Run().

Where do I have a problem with executing the steps with Task.WhenAll?

Edit: Calling code after I changed the Run method to async Task RunAsync:

private static async void doIt() {
  var step = new NoWorkStep();
  await step.RunAsync();
}
like image 546
Sascha Avatar asked Apr 21 '15 06:04

Sascha


People also ask

Do tasks run in parallel?

Concurrent tasks progress at the same time in the worker system but they don't progress simultaneously. Parallel tasks are executed by different workers at the same time. Concurrency refers to how a worker system handles multiple tasks while parallelism refers to how a worker system handles a single task.


2 Answers

Lets map out the problems with your code:

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

This returns a cold Task, meaning the Task isn't actually started. In order for it to start you would need to call:

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

But, you shouldn't use new Task anyway, you should use Task.Run.

If I use instead await Task.Run( () => step.Run() ); it awaits only the first one and resumes the main thread.

That is because Run is async void which cannot be awaited. async void is ment to be used only in top level event handlers, where this clearly isn't the case here.

If you want to await on until all the tasks are completed, you can do that following:

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

This will guarantee all tasks have completed execution once RunAsync finishes.

like image 181
Yuval Itzchakov Avatar answered Oct 10 '22 16:10

Yuval Itzchakov


You don't seem to be starting the tasks.

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);
like image 39
RagtimeWilly Avatar answered Oct 10 '22 17:10

RagtimeWilly