假设我们必须通过异步流程在数据库上写下1000个元素的列表。最好等待1000次异步插入语句,或者将所有1000个插入都封装在封装到Task.Run语句中的单个同步方法中,等待一次?

例如,SqlCommand的所有方法都与async版本结合在一起。在这种情况下,我们有一个插入语句,因此我们可以调用ExecuteNonQueryExecuteNonQueryAsync

通常,根据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线程上安排继续操作。

10-04 11:08
查看更多