运行 没有 Task.WhenAll 的并行异步操作
Run async operation in parallel without Task.WhenAll
我需要 运行 三个并行的异步 I/O 操作,特别是它们是数据库调用。所以,我写了下面的代码:
// I need to know whether these tasks started running here
var task1 = _repo.GetThingOneAsync();
var task2 = _repo.GetThingTwoAsync();
var task3 = _repo.GetThingThreeAsync();
// await the results
var task1Result = await task1;
var task2Result = await task2;
var task3Result = await task3;
GetThingOneAsync(), GetThingTwoAsync(), GetThingThreeAsync()
方法彼此非常相似,只是它们具有不同的 return 类型(Task<string>, Task<int>, Task<IEnumerable<int>>
)。其中一个数据库调用的示例如下:
public async Task<IEnumerable<int>> GetThingOneAsync(string code)
{
return await db.DrType.Where(t => t.Code == code).Select(t => t.IdType).ToListAsync();
}
在调试模式下,我可以看到 var task1 = _repo.GetThingOneAsync();
开始 运行 GetThingOneAsync()
异步方法(与其他两个任务相同)。
同事说_repo.GetThingOneAsync()
不启动异步操作。他们说当我们到达 await
时就开始了操作(这种说法对我来说似乎是错误的)。
因此,他们建议将我的代码修改为以下内容:
var task1 = _repo.GetThingOneAsync();
var task2 = _repo.GetThingTwoAsync();
var task3 = _repo.GetThingThreeAsync();
await Task.WhenAll(task1, task2, task3);
// then get the result of each task through `Result` property.
在我看来,除了Task.WhenAll
如果前面的任务出错,等待后面的任务完成之外,这和我在问题开头写的一样 (这是来自 this question 的 Servy 评论)
我知道我的问题有点重复,但我想知道我做的事情是对还是错。
我同意@MickyD,任务已在初始调用时创建。这两个调用在效果上是相似的。
虽然有一些细微差别。当您调用 GetThingOneAsync 方法时,它会一直执行到到达 await 语句为止;那就是它 return 的任务。如果 Async 方法从不执行 await 则它退出并且 returns 一个已经完成的任务。因此,如果这些是计算密集型例程(看起来不像),那么您将无法实现任何并行性。您需要使用 Task.Run 来实现同时执行。另一点是,如果您从 UI 线程使用 await,那么所有执行都将在 UI 线程上进行——只是在不同的时间安排。如果 Task 正在执行 IO,这有点 OK,因为它会阻塞 read/write。然而,它可以开始累加,所以如果你打算做任何实质性的事情,那么你应该把它放在线程池中(即 Task.Run)。
关于您同事的评论,正如我所说,任务 1、2、3 确实在等待之前 运行 开始。但是当你点击 await 时,你当前正在执行的方法将暂停并 return 一个任务。因此,创建任务的是 await 在某种程度上是正确的——只是您在问题 (task1,2,3) 中考虑的任务是在 GetThingXxxAsync 遇到 await 时创建的任务,而不是在您的任务时创建的任务主程序等待任务 1、2、3。
My colleagues say that _repo.GetThingOneAsync() does not start the async operation. They say that the operation started when we reach await (that statement seems to be wrong for me).
他们错了。调用方法时开始操作。
这很容易通过启动具有一些可观察到的副作用的操作(如写入数据库)来证明。调用该方法然后阻止应用程序,例如使用 Console.ReadKey()
。然后您将看到操作完成(在数据库中),没有 await
.
问题的其余部分都是关于文体偏好的。这些选项之间存在细微的语义差异,但通常并不重要。
var task1 = _repo.GetThingOneAsync();
var task2 = _repo.GetThingTwoAsync();
var task3 = _repo.GetThingThreeAsync();
// await the results
var task1Result = await task1;
var task2Result = await task2;
var task3Result = await task3;
上面的代码将 await
(异步等待)每个任务完成,一次一个。如果所有三个都成功完成,则此代码等同于 Task.WhenAll
方法。不同之处在于,如果 task1
或 task2
有异常,则永远不会等待 task3
。
这是我个人最喜欢的:
var task1 = _repo.GetThingOneAsync();
var task2 = _repo.GetThingTwoAsync();
var task3 = _repo.GetThingThreeAsync();
await Task.WhenAll(task1, task2, task3);
var task1Result = await task1;
var task2Result = await task2;
var task3Result = await task3;
我喜欢 Task.WhenAll
因为它很明确。看代码,很明显是异步并发,因为那里有个Task.WhenAll
我喜欢使用 await
而不是 Result
,因为它对代码更改更有弹性。特别是,如果 不 喜欢 Task.WhenAll
的人出现并删除了它,您仍然会 await
完成这些任务而不是使用 Result
,这可能会导致死锁并在 AggregateException
中包装异常。 Result
在 Task.WhenAll
之后工作的唯一原因是因为这些任务已经完成 和 它们的异常已经被观察到。
但这主要是个人意见。
我需要 运行 三个并行的异步 I/O 操作,特别是它们是数据库调用。所以,我写了下面的代码:
// I need to know whether these tasks started running here
var task1 = _repo.GetThingOneAsync();
var task2 = _repo.GetThingTwoAsync();
var task3 = _repo.GetThingThreeAsync();
// await the results
var task1Result = await task1;
var task2Result = await task2;
var task3Result = await task3;
GetThingOneAsync(), GetThingTwoAsync(), GetThingThreeAsync()
方法彼此非常相似,只是它们具有不同的 return 类型(Task<string>, Task<int>, Task<IEnumerable<int>>
)。其中一个数据库调用的示例如下:
public async Task<IEnumerable<int>> GetThingOneAsync(string code)
{
return await db.DrType.Where(t => t.Code == code).Select(t => t.IdType).ToListAsync();
}
在调试模式下,我可以看到 var task1 = _repo.GetThingOneAsync();
开始 运行 GetThingOneAsync()
异步方法(与其他两个任务相同)。
同事说_repo.GetThingOneAsync()
不启动异步操作。他们说当我们到达 await
时就开始了操作(这种说法对我来说似乎是错误的)。
因此,他们建议将我的代码修改为以下内容:
var task1 = _repo.GetThingOneAsync();
var task2 = _repo.GetThingTwoAsync();
var task3 = _repo.GetThingThreeAsync();
await Task.WhenAll(task1, task2, task3);
// then get the result of each task through `Result` property.
在我看来,除了Task.WhenAll
如果前面的任务出错,等待后面的任务完成之外,这和我在问题开头写的一样 (这是来自 this question 的 Servy 评论)
我知道我的问题有点重复,但我想知道我做的事情是对还是错。
我同意@MickyD,任务已在初始调用时创建。这两个调用在效果上是相似的。
虽然有一些细微差别。当您调用 GetThingOneAsync 方法时,它会一直执行到到达 await 语句为止;那就是它 return 的任务。如果 Async 方法从不执行 await 则它退出并且 returns 一个已经完成的任务。因此,如果这些是计算密集型例程(看起来不像),那么您将无法实现任何并行性。您需要使用 Task.Run 来实现同时执行。另一点是,如果您从 UI 线程使用 await,那么所有执行都将在 UI 线程上进行——只是在不同的时间安排。如果 Task 正在执行 IO,这有点 OK,因为它会阻塞 read/write。然而,它可以开始累加,所以如果你打算做任何实质性的事情,那么你应该把它放在线程池中(即 Task.Run)。
关于您同事的评论,正如我所说,任务 1、2、3 确实在等待之前 运行 开始。但是当你点击 await 时,你当前正在执行的方法将暂停并 return 一个任务。因此,创建任务的是 await 在某种程度上是正确的——只是您在问题 (task1,2,3) 中考虑的任务是在 GetThingXxxAsync 遇到 await 时创建的任务,而不是在您的任务时创建的任务主程序等待任务 1、2、3。
My colleagues say that _repo.GetThingOneAsync() does not start the async operation. They say that the operation started when we reach await (that statement seems to be wrong for me).
他们错了。调用方法时开始操作。
这很容易通过启动具有一些可观察到的副作用的操作(如写入数据库)来证明。调用该方法然后阻止应用程序,例如使用 Console.ReadKey()
。然后您将看到操作完成(在数据库中),没有 await
.
问题的其余部分都是关于文体偏好的。这些选项之间存在细微的语义差异,但通常并不重要。
var task1 = _repo.GetThingOneAsync();
var task2 = _repo.GetThingTwoAsync();
var task3 = _repo.GetThingThreeAsync();
// await the results
var task1Result = await task1;
var task2Result = await task2;
var task3Result = await task3;
上面的代码将 await
(异步等待)每个任务完成,一次一个。如果所有三个都成功完成,则此代码等同于 Task.WhenAll
方法。不同之处在于,如果 task1
或 task2
有异常,则永远不会等待 task3
。
这是我个人最喜欢的:
var task1 = _repo.GetThingOneAsync();
var task2 = _repo.GetThingTwoAsync();
var task3 = _repo.GetThingThreeAsync();
await Task.WhenAll(task1, task2, task3);
var task1Result = await task1;
var task2Result = await task2;
var task3Result = await task3;
我喜欢 Task.WhenAll
因为它很明确。看代码,很明显是异步并发,因为那里有个Task.WhenAll
我喜欢使用 await
而不是 Result
,因为它对代码更改更有弹性。特别是,如果 不 喜欢 Task.WhenAll
的人出现并删除了它,您仍然会 await
完成这些任务而不是使用 Result
,这可能会导致死锁并在 AggregateException
中包装异常。 Result
在 Task.WhenAll
之后工作的唯一原因是因为这些任务已经完成 和 它们的异常已经被观察到。
但这主要是个人意见。