LINQ代码中的异步-澄清

本文关键字:澄清 异步 代码 LINQ | 更新日期: 2023-09-27 18:03:31

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

LINQ不能与异步完美配合

还有:

我建议您不要将此视为"在LINQ中使用异步">

但在斯蒂芬的书中有一个例子:

问题:您有一系列任务要等待,并且您想做一些在每个任务完成后对其进行处理。但是,你想每个的处理一旦完成,而不是等待任何其他任务。

其中一个推荐的解决方案是:

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:

我不明白为什么现在LINQ语句中的async确实有效。我们不是说"不要考虑在LINQ中使用async"吗?

问题2:

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

LINQ代码中的异步-澄清

我不明白为什么现在LINQ语句中的async确实有效。我们不是说"不要考虑在LINQ中使用异步"吗?

async大多不适用于LINQ,因为IEnumerable<T>扩展并不总是正确地推断委托类型,并遵循Action<T>。他们对Task类没有特别的了解。这意味着实际的异步委托变成了async void,这很糟糕。在Enumerable.Select的情况下,我们有一个重载,它返回一个Func<T>(在我们的情况下将是Func<Task>(,这相当于async Task,因此它适用于异步用例。

当控件到达等待t时,实际发生了什么?控件是否离开ProcessTasksAsync方法?

不,不是。CCD_ 14是关于投影序列中的所有元素。这意味着,对于集合中的每个元素,await t将把控制权交还给迭代器,迭代器将继续迭代所有元素。这就是为什么以后必须使用await Task.WhenAll,以确保所有元素都已完成执行。

问题1:

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

问题2:

当控件到达等待t时,实际发生了什么?

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

最后,我们有await Task.WhenAll(processingTasks);,它将等待包括额外处理(Trace.WriteLine(result);(在内的所有任务在继续之前完成,但每个任务都不等待其他任务继续执行: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返回Task。