许多等待Async方法,或者单个等待包装任务

本文关键字:等待 单个 包装 任务 或者 Async 方法 许多 | 更新日期: 2023-09-27 18:17:14

假设我们必须通过异步流在数据库上写下一个包含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的整个持续时间内自由地更新View(例如一个旋转器或进度条),也就是说,用户不会感觉到延迟。

我说的对吗?还是我错过了什么"异步"?

我不是在寻找简单的基于意见的答案,我在寻找async/await指南的解释,在这种情况下,以及解决它的最佳方法。

编辑:

我读过这个问题,但它是不一样的。这个问题是关于在异步方法上选择单个await还是单个Task.Run await。这个问题是关于调用1000 await的后果和由于线程之间的持续切换而导致的资源开销。

许多等待Async方法,或者单个等待包装任务

你的分析基本上是正确的。你似乎高估了这会给UI线程带来的负担;它被要求做的实际工作是相当小的,所以它能够保持良好的可能性,但也有可能你做的足够多,它不能,所以你有兴趣在UI线程上不执行延续是正确的。

你错过的当然是避免所有UI线程回调的首选方法。当您await一个操作时,如果您实际上不需要方法的其余部分返回到原始上下文,您可以简单地将ConfigureAwait(false)添加到您正在等待的任务的末尾。这将阻止continuation在当前上下文(即UI线程)中运行,而让continuation在线程池线程中运行。

使用ConfigureAwait(false)允许您避免UI负责不必要的非UI工作,同时也防止您需要调度线程池线程做更多的工作。

当然,如果你的工作结束后做的工作实际上是要做UI工作,那么该方法不应该使用ConfigureAwait(false);,因为它实际上希望在UI线程上调度continuation。

这一切都取决于UI所期望的。如果UI依赖于操作的完成,以保持一个有效的状态,那么你别无选择,只能等待异步任务,或者像往常一样在主UI线程上使用调用。

对于需要很长时间但不是调用线程立即需要的任务,或者调用线程依赖于操作的结果的任务,线程化是很好的。

任务的存在不是为了加快速度,而是为了提高效率。因此,你可以保留任务线程并引发一个带有"操作完成"的事件,让UI在操作发生时正常运行,但就像我说的,如果UI线程依赖于结果,你别无选择,只能等待。

如果你通过事件路由,你可以异步更新你的UI与结果,因为他们进来

这两个解决方案之间有一个重要的区别。第一个是单线程的(除非你使用ConfigureAwait(false) @Servy建议),而第二个是多线程的。

多线程总是给你的程序带来额外的复杂性。你应该先问问自己,你是否愿意用这些来换取你可能获得的好处。