【发布时间】:2021-06-30 19:11:34
【问题描述】:
我有一个关于过度使用 async-await 运算符的问题。
我有一个Zoo 实例列表,我需要将每个Zoo 的每个成员分别插入到数据库中。这是Zoo 类定义:
class Zoo
{
public Dog Dog { get; set; }
public Cat Cat { get; set; }
public Bear Bear { get; set; }
}
下面提供了插入Zoo的方法。
Zoo 的每个实例及其成员都应该在插入之前进行验证或转换,因此我使用 Parallel.For 处理它们:
public void Save(List<Zoo> zoos)
{
Parallel.For(0, zoos.Count, new ParallelOptions { MaxDegreeOfParallelism = 4 }, async i =>
{
...
Task saveDog = Task.Run(async () => await InsertDogAsync(zoos[i], string connectionString));
Task saveCat = Task.Run(async () => await InsertCatAsync(zoos[i], string connectionString));
Task saveBear = Task.Run(async () => await InsertBearAsync(zoos[i], string connectionString));
await Task.WhenAll(new Task[] { saveDog, saveCat, saveBear });
...
});
}
我使用async 方法将动物插入数据库。例如这里是Dog:
private static async Task InsertDogAsync(Zoo zoo, string connectionString)
{
using SqlConnection connection = new SqlConnection(connectionString);
await connection.OpenAsync();
...
using SqlCommand insertCommand = new SqlCommand("sp_insertDog", connection); // stored procedure
...
await insertCommand.ExecuteNonQueryAsync();
}
我的问题如下:是否过度使用 async-await 运算符?正如我所意识到的,每个await 运算符在完成之前都会释放线程,但是在并行任务中调用方法,因此线程用于不同的任务。也许从Task.Run lambda 中删除一些async-await 更容易接受?
【问题讨论】:
-
如果任务是 IO 绑定的,我假设它们是,那么
Task.Run并不是真正需要的。我也怀疑你真的需要做Parallel.For。真正的问题不是过度使用 async/await,而是在使用中,因为你应该从上到下使用它,所以Save方法应该是 async 并返回一个Task并且任何调用它都应该是 async一直到顶部。此外,InsertDogAsync实际上应该是异步的并且还返回一个Task。事实上,代码不会编译,因为您不能在不是async的方法中包含await。 -
可能是我的 C# 离我有点远,但是......为什么你觉得你必须在
Task.Run调用中包装任务?为什么不去Task saveDog = InsertDogAsync(zoos[i], connectionString);? -
@juharr 对不起,我在
InsertDogAsync错过了async Task,当然它会返回Task。 -
几件事:看起来您正在尝试进行过早优化。别担心我们都去过那里。 99% 的时间你不需要像那样并行地做任何事情。它不会让它更快,尤其是在数据库方面。 2. 您应该尝试使用 EntityFramework Core。一开始设置很痛苦,但你会非常喜欢它。 3. 我并没有真正注意到过多使用 async-await 对性能造成的影响,但我(几乎)从不执行 Task.Run,除非我必须这样做。无论如何,EF Core 都会为您处理所有这些事情
-
我会强烈建议批量插入,这将首先排除并行化的必要性,并且要快得多
标签: c# .net sql-server async-await parallel.for