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 ?

2
  • 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. Commented Jul 18, 2015 at 9:43
  • A good read by Stephen Toub, if you haven't come across it yet: Tasks, Monads, and LINQ. Commented Jul 18, 2015 at 11:02

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

Comments

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

Comments

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.