LINQ 代码中的异步 - 说明?

async within a LINQ code - Clarification?

几乎每个 SO 关于此主题的回答都指出:

LINQ doesn't work perfectly with async

还有:

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

但是在 Stephen 的书中有一个示例:

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.

推荐的解决方案之一是:

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

}

问题 #1:

我不明白为什么现在 async 在 LINQ 语句中可以工作。我们不是刚刚说过“不要考虑在 LINQ 中使用 async”吗?

问题 #2:

当控件到达 await t 此处时 — 实际发生了什么?控件是否离开 ProcessTasksAsync 方法?还是离开匿名方法并继续迭代?

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 大部分 不适用于 LINQ,因为 IEnumerable<T> 扩展并不总是正确地推断委托类型并遵循 Action<T>.他们对Taskclass没有特别的了解。这意味着实际的异步委托变为 async void,这很糟糕。在 Enumerable.Select 的情况下,我们有一个重载 returns a Func<T>(在我们的例子中又是 Func<Task>),它等同于 async Task,因此它适用于异步用例。

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

不,不是。 Enumerable.Select 是关于投影序列中的所有元素。这意味着对于集合中的每个元素,await t 会将控制权交还给迭代器,迭代器将继续迭代所有元素。这就是您稍后必须 await Task.WhenAll 以确保所有元素都已完成执行的原因。

问题一:

不同之处在于每个任务 继续 并进行额外处理,即:Trace.WriteLine(result);。在您指向的 link 中,该代码不会改变任何内容,只会产生等待和包装另一个任务的开销。

问题二:

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

等待ProcessTasksAsync的任务结果,然后继续Trace.WriteLine(result);。我们可以说当我们得到结果时控件离开了ProcessTasksAsync方法并且处理仍在匿名方法中。

最后,我们 await Task.WhenAll(processingTasks); 将等待所有任务完成,包括附加处理 (Trace.WriteLine(result);),然后再继续,但 每个任务不会等待others 继续执行:Trace.WriteLine(result);

这样会更好:

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

任务数组,因为 AwaitAndProcessAsync returns 任务。