假设我们必须通过异步流程在数据库上写下1000个元素的列表。最好等待1000次异步插入语句,或者将所有1000个插入都封装在封装到Task.Run
语句中的单个同步方法中,等待一次?
例如,SqlCommand
的所有方法都与async
版本结合在一起。在这种情况下,我们有一个插入语句,因此我们可以调用ExecuteNonQuery
或ExecuteNonQueryAsync
。
通常,根据async / await指南,我们读到,如果您有某种方法可用的异步版本,则应使用它。所以假设我们写:
async Task Save(IEnumerable<Savable> savables)
{
foreach(var savable in savables)
{
//create SqlCommand somehow
var sqlCmd = CreateSqlCommand(savable);
//use asynchronous version
await sqlCmd.ExecuteNonQueryAsync();
}
}
这段代码很清楚。但是,每次它从等待部分退出时,它还会在UI线程上返回,然后在遇到的下一个等待中返回后台线程,依此类推(不是吗?)。这意味着用户可能会看到一些滞后,因为UI线程被
await
的连续执行而连续中断,以执行下一个foreach
周期,并且在那部分时间内UI冻结了一段时间。我想知道我是否更好地编写如下代码:
async Task Save(IEnumerable<Savable> savables)
{
await Task.Run(() =>
{
foreach(var savable in savables)
{
//create SqlCommand somehow
var sqlCmd = CreateSqlCommand(savable);
//use synchronous version
sqlCmd.ExecuteNonQuery();
}
});
}
这样,整个
foreach
在辅助线程上执行,而无需在UI线程和辅助线程之间进行连续切换。这意味着UI线程可以在
foreach
的整个持续时间内自由更新视图(例如,微调器或进度条),也就是说,用户不会感觉到滞后。我对吗?还是我缺少有关“一直向下同步”的内容?
我不是在寻找基于意见的简单答案,而是在寻求有关异步/等待准则的解释,以寻求最佳解决方案。
编辑:
我读过this question,但不一样。这个问题是关于在异步方法上选择单个
await
而不是单个Task.Run
等待。这个问题是关于调用1000 await
的后果以及由于线程之间不断切换而导致的资源开销的问题。 最佳答案
您的分析在很大程度上是正确的。您似乎高估了这将给UI线程带来的负担。它会被要求做的实际工作量很小,因此很有可能保持良好状态,但是您可能已经做了足够的工作而做不到,所以您正确地做有兴趣不对UI线程执行延续。
当然,您缺少的是避免所有回调UI线程的首选方法。 await
操作时,如果实际上不需要该方法的其余部分返回到原始上下文,则可以简单地将ConfigureAwait(false)
添加到等待的任务的末尾。这将防止继续操作在当前上下文(UI线程)中运行,而是让继续操作在线程池线程中运行。
使用ConfigureAwait(false)
可以避免UI不必要地负责非UI工作,同时还可以避免安排线程池线程进行更多的工作。
当然,如果您在继续操作之后最终要完成的工作实际上是要进行UI工作,则该方法不应使用ConfigureAwait(false);
,因为它实际上是想在UI线程上安排继续操作。