等待Task.WhenAll与选择(async.. => await)的区别

8

我是一名有帮助的助手,可以为您进行文本翻译。

只是一个快速的问题。我们这里有一些误解。

我们有:

var tasks = files.Select(async fileName => await IngestFileAsync(container, fileName));
var results = await Task.WhenAll(tasks);

我认为第一行仍然是并发执行的,但我的同事持有不同意见。此外,他认为第二个await没有意义,因为所有操作都已经完成。

那么这段代码是否相同:

var tasks = files.Select(fileName => IngestFileAsync(container, fileName));
var results = await Task.WhenAll(tasks);

作为:

var tasks = files.Select(async fileName => await IngestFileAsync(container, fileName));
var results = Task.WhenAll(tasks);

能有人对此进行更详细的解释吗?

谢谢。

添加:

好的,所以它将并发运行。

但是,有人能否添加一些额外的信息来解释这些代码片段之间的区别:https://dotnetfiddle.net/lzv2B7https://dotnetfiddle.net/dMusus

(注意第16行,asyncawait)。这两者之间有什么区别吗? 我预计使用asyncawait会直接开始,而不使用则会在Await Task.WhenAll(tasks);时开始。

为了清晰起见,这是我的代码:

   private async Task<Result> IngestFilesAsync(ICloudBlobContainer container, IEnumerable<string> files)
    {
        _logger.LogDebug("Start IngestFilesAsync");

        var tasks = files.Select(fileName => IngestFileAsync(container, fileName));
        var results = await Task.WhenAll(tasks);

        _logger.LogDebug("All tasks completed");

        if (results.Any(t => t.IsFailure))
        {
            return Result.Fail(string.Join(",", results.Select(f => f.Error)));
        }

        return Result.Ok();
    }

    private async Task<Result> IngestFileAsync(ICloudBlobContainer container, string fileName)
    {
        _logger.LogDebug("Start IngestFileAsync");
        var blob = container.GetBlockBlobReference(fileName);

        _logger.LogDebug("Blob retrieved");

        if (await blob.ExistsAsync())
        {
            using (var memoryStream = new MemoryStream())
            {
                _logger.LogDebug("Start download to stream");
                await blob.DownloadToStreamAsync(memoryStream);
                _logger.LogDebug("To mem downloaded");

                _logger.LogDebug("Start ftp-upload");

                return await _targetFTP.UploadAsync(memoryStream, fileName);
            }
        }

        _logger.LogWarning("Blob does not exists");

        return Result.Fail($"Blob '{fileName}' does not exist");
    }

在这里,_targetFTP.UploadAsync(memoryStream, fileName); 是另一个任务等等。


1
只需将这两种情况重写为foreach循环(而不是Select)- 您就可以立即得到答案。 - Fabio
这很容易测试:让您的异步函数Task.Delay并测量所需时间。 - user743382
@fidor 在我的日志记录中,我看到了连续重复的行,这表明它们是并发执行的。现在我打字时,可能有多个线程会到达这里。 - Roelant M
请取消采纳我的答案...第一个版本是错误的,而且还有更好的答案。 - gdoron
@Fildor 这个推理与第二个代码片段中操作并发执行的原因是相同的。启动一堆异步操作而不等待任何一个完成,除非特定实现禁止它,否则将导致它们同时执行其工作。 - Servy
显示剩余2条评论
1个回答

10

async x => await f() 创建一个返回任务的匿名函数。该任务有效地包装了由 f 创建的任务:它将直接在其后完成。特别是,这个匿名函数尽可能快地返回一个正在进行中的任务。

.Select 不会根据枚举类型是否为任务而表现不同。当第一个返回的任务仍在进行中时,它允许直接获取下一个结果。

代码片段并不完全相同,但你所询问的差异并不存在。

这些差异很小;最明显的变化可能在异常处理方面。假设你有两个尚未实现的函数:

Task Sync() => throw new NotImplementedException();
async Task Async() => throw new NotImplementedException();

在这里,var task = Sync(); 显然会立即失败。但是 var task = Async(); 不同:它成功了。这里的 async 关键字强制创建一个任务,该任务捕获抛出的异常。
同样的区别也适用于 .Select(x => Sync()).Select(async x => await Sync())

好的,所以它仍然是并发的。第二个 await => var results = await Task.WhenAll(tasks); 仍然有意义吗,还是我可以只使用 Task.WhenAll(tasks);? - Roelant M
@RoelantM Task.WhenAll(tasks) 不会阻塞。它返回另一个任务,该任务在所有原始任务完成时完成。因此,只有 Task.WhenAll(tasks); 是没有效果的。但是,如果您不需要结果,可以使用 await Task.WhenAll(tasks); 而不是 var results - user743382
因此,应该使用await,否则在所有线程准备好之前,它将继续执行函数的其余部分。您能解释一下在select中使用async和await与不使用它们之间的区别吗? - Roelant M
@RoelantM 编辑以涵盖其中的一个差异。 - user743382
谢谢!那么对于异常处理来说,.select(async .. => await) 是一种更好的方法吗? - Roelant M
如果你调用的函数承诺始终返回一个任务,即使它可能是一个失败的任务,那么对于异常处理来说就无关紧要了。我不知道你正在调用哪个函数。 - user743382

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接