10

Almost every SO's answer regarding this topic , states that :

LINQ doesn't work perfectly with async

Also :

I recommend that you not think of this as "using async within LINQ"

But in Stephen's book there is a sample for :

Problem: You have a collection of tasks to await, and you want to do some processing on each task after it completes. However, you want to do the processing for each one as soon as it completes, not waiting for any of the other tasks.

One of the recommended solutions was :

static async Task<int> DelayAndReturnAsync(int val)
{
 await Task.Delay(TimeSpan.FromSeconds(val));
 return val;
}

// This method now prints "1", "2", and "3".
static async Task ProcessTasksAsync()
{
 // Create a sequence of tasks.
 Task<int> taskA = DelayAndReturnAsync(2);
 Task<int> taskB = DelayAndReturnAsync(3);
 Task<int> taskC = DelayAndReturnAsync(1);
 var tasks = new[] { taskA, taskB, taskC };
 var processingTasks = tasks.Select(async t =>
    {
    var result = await t;
    Trace.WriteLine(result);
    }).ToArray();

// Await all processing to complete
await Task.WhenAll(processingTasks);

}

Question #1:

I don't understand why now async inside a LINQ statement - does work . Didn't we just say "don't think about using async within LINQ" ?

Question #2:

When the control reaches the await t here — What is actually happen? Does the control leaves the ProcessTasksAsync method ? or does it leaves the anonymous method and continue the iteration ?

Community
  • 1
  • 1
Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • That "don't think about ..." statement wasn't imperative. None of the texts you quote say that async won't work with Linq - just hat it's not a perfect match. – H H Jul 18 '15 at 09:43
  • A good read by Stephen Toub, if you haven't come across it yet: [Tasks, Monads, and LINQ](http://blogs.msdn.com/b/pfxteam/archive/2013/04/03/tasks-monads-and-linq.aspx). – noseratio Jul 18 '15 at 11:02

3 Answers3

9

I don't understand why now async inside a LINQ statement - does work . Didn't we just say "don't think about using async within LINQ" ?

async mostly doesn't work with LINQ because IEnumerable<T> extensions don't always infer the delegate type properly and defer to Action<T>. They have no special understanding of the Task class. This means the actual async delegate becomes async void, which is bad. In the case of Enumerable.Select, we have an overload which returns a Func<T> (which in turn will be Func<Task> in our case), which is equivalent to async Task, hence it works fine for async use-cases.

When the control reaches the await t here — What is actually happen? Does the control leaves the ProcessTasksAsync method ?

No, it doesn't. Enumerable.Select is about projecting all elements in the sequence. This means that for each element in the collection, await t which will yield control back to the iterator, which will continue iterating all elements. That's why you later have to await Task.WhenAll, to ensure all elements have finished execution.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
3

Question 1:

The difference is that each task is continued with additional processing which is: Trace.WriteLine(result);. In the link you pointed to, that code does not change anything, just creates overhead of awaiting and wrapping with another task.

Question 2:

When the control reaches the await t here — What is actually happen?

It awaits for the result of ProcessTasksAsync's task, then continue with Trace.WriteLine(result);. We can say that the control leaves the ProcessTasksAsync method when we have the result and the processing is still inside the anonymous method.

At the end, we have await Task.WhenAll(processingTasks); which will await for all tasks including the additional processing (Trace.WriteLine(result);) to complete before continuing but each task does not await for the others to continue executing: Trace.WriteLine(result);

Khanh TO
  • 48,509
  • 13
  • 99
  • 115
0

It will be better this way:

static async Task<int> DelayAndReturnAsync(int val)
{
    await Task.Delay(TimeSpan.FromSeconds(val));
    return val;
}
static async Task AwaitAndProcessAsync(Task<int> task)
{
    var result = await task;
    Console.WriteLine(result);
}
// This method now prints "1", "2", and "3".
static async Task ProcessTasksAsync()
{
    // Create a sequence of tasks.
    Task<int> taskA = DelayAndReturnAsync(2);
    Task<int> taskB = DelayAndReturnAsync(3);
    Task<int> taskC = DelayAndReturnAsync(1);
    var tasks = new[] { taskA, taskB, taskC };
    var processingTasks = tasks.Select(AwaitAndProcessAsync).ToArray();
    // Await all processing to complete
    await Task.WhenAll(processingTasks);
}

Array of Task, because AwaitAndProcessAsync returns Task.