1

I'm looping through an Array of values, for each value I want to execute a long running process. Since I have multiple tasks to be performed that have no inter dependency I want to be able to execute them in parallel.

My code is:

List<Task<bool>> dependantTasksQuery = new List<Task<bool>>();

foreach (int dependantID in dependantIDList)
{   
    dependantTasksQuery.Add(WaitForDependantObject(dependantID));                                
}

Task<bool>[] dependantTasks = dependantTasksQuery.ToArray();

//Wait for all dependant tasks to complete
bool[] lengths = await Task.WhenAll(dependantTasks);

The WaitForDependantObject method just looks like:

async Task<bool> WaitForDependantObject(int idVal)
{
    System.Threading.Thread.Sleep(20000);

    bool waitDone = true;            

    return waitDone;
}

As you can see I've just added a sleep to highlight my issue. What is happening when debugging is that on the line:

dependantTasksQuery.Add(WaitForDependantObject(dependantID));   

My code is stopping and waiting the 20 seconds for the method to complete. I did not want to start the execution until I had completed the loop and built up the Array. Can somebody point me to what I'm doing wrong? I'm pretty sure I need an await somewhere

i3arnon
  • 113,022
  • 33
  • 324
  • 344
keitn
  • 1,288
  • 2
  • 19
  • 43
  • Please post code that compiles - `WaitForDependantObject` have no chance to be compilable as shown. Note that `Sleep` should be used to imitate long CPU intesive task, use `Task.Delay` as replacement for long IO bound work. – Alexei Levenkov Oct 15 '14 at 15:55
  • Should compile now, thread.sleep is purely to force a delay. This is just a sample and not my actual code, it's only used to demonstrate my issue. – keitn Oct 15 '14 at 15:59
  • 1
    You don't have any asynchronous methods. `WaitForDependantObject` blocks synchronously. `async` doesn't make a method asynchronous, it allows you to use the `await` keyword inside its body. – Panagiotis Kanavos Oct 15 '14 at 16:01
  • I've added answer showing proper "sample of async method" (which would work with original code, still would show delays for "synchronous part" and if using WPF/WinForms all results come back to original thread). – Alexei Levenkov Oct 15 '14 at 16:11

3 Answers3

4

In your case WaitForDependantObject isn't asynchronous at all even though it returns a task. If that's your goal do as Luke Willis suggests. To make these calls both asynchronous and truly parallel you need to offload them to a Thread Pool thread with Task.Run:

bool[] lengths = await Task.WhenAll(dependantIDList.Select(() => Task.Run(() => WaitForDependantObject(dependantID))));

async methods run synchronously until an await is reached and them returns a task representing the asynchronous operation. In your case you don't have an await so the methods simply execute one after the other. Task.Run uses multiple threads to enable parallelism even on these synchronous parts on top of the concurrency of awaiting all the tasks together with Task.WhenAll.

For WaitForDependantObject to represent an async method more accurately it should look like this:

async Task<bool> WaitForDependantObject(int idVal)
{
    await Task.Delay(20000);
    return true;
}
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • Gotcha, Luke's answer accomplishes what I need so I've accepted it. I've Up-voted this answer as it is good to know for future reference. – keitn Oct 15 '14 at 16:06
  • @keitn Then I would suggest updating your title, as your question doesn't regard `async` calls – i3arnon Oct 15 '14 at 16:08
0

Use Task.Delay to make method asynchronous and looking more real replacement of mocked code:

async Task<bool> WaitForDependantObject(int idVal)
{
   // how long synchronous part of method takes (before first await)
   System.Threading.Thread.Sleep(1000);

   // method returns as soon as awiting started    
   await Task.Delay(2000); // how long IO or other async operation takes place

   // simulate data processing, would run on new thread unless 
   // used in WPF/WinForms/ASP.Net and no call to ConfigureAwait(false) made by caller.
   System.Threading.Thread.Sleep(1000);

   bool waitDone = true;            

   return waitDone;
}
i3arnon
  • 113,022
  • 33
  • 324
  • 344
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
-1

You can do this using Task.Factory.StartNew.

Replace this:

dependantTasksQuery.Add(WaitForDependantObject(dependantID));

with this:

dependantTasksQuery.Add(
    Task.Factory.StartNew(
        () => WaitForDependantObject(dependantID)
    )
);

This will run your method within a new Task and add the task to your List.

You will also want to change the method signature of WaitForDependantObject to be:

bool WaitForDependantObject(int idVal)

You can then wait for your tasks to complete with:

Task.WaitAll(dependentTasksQuery.ToArray());

And get your results with:

bool[] lengths = dependentTasksQuery.Select(task => task.Result).ToArray();
Luke Willis
  • 8,429
  • 4
  • 46
  • 79
  • 1
    Even if the calls are not asynchronous you should still you `await Task.WhenAll` instead of blocking the calling thread. – i3arnon Oct 15 '14 at 16:25
  • @I3arnon So, I guess I've been doing asynchronous wrong in C#. I just read [this](http://stackoverflow.com/q/6123406/2479481). It seems to me that `Task.WaitAll` *could* be appropriate *if* you need the results of your `Task`s in order to continue. Is this correct? – Luke Willis Oct 15 '14 at 16:33
  • `Task.WhenAll` returns the results, `Task.WaitAll` doesn't. The issue here is blocking vs asynchronously waiting for completion. blocking is usually bad as it uses up a thread without executing anything and so hinders scalability. – i3arnon Oct 15 '14 at 16:46