许多等待Async方法,或者一次等待包装Task.Run? [英] Many awaits for Async method, or a single await for a wrapping Task.Run?

查看:74
本文介绍了许多等待Async方法,或者一次等待包装Task.Run?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Suppose we have to write down on database a list of 1000 elements, through an async flow. Is it better to await 1000 times an asynchronous insert statement, or to wrap all the 1000 inserts in one single synchronous method encapsulated into a Task.Run statement, awaiting one single time?

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

For example, SqlCommand has every method coupled with his async version. In this case, we have an insert statement, so we can call ExecuteNonQuery or ExecuteNonQueryAsync.

通常,根据异步/等待准则,我们读到,如果您有某种方法可用的异步版本,则应使用它.所以假设我们写:

Often, on async/await guidelines, we read that if you have an asynchronous version available for some method, you should use it. So suppose we write:

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线程上返回,然后在遇到的下一个等待中返回后台线程,依此类推(不是吗?).这意味着用户可能会看到一些滞后,因为await的连续执行连续中断了UI线程,以执行下一个foreach周期,并且在这段时间内,UI冻结了一点.

This code is very clear. However, every time it goes out from the await part, it also returns on the UI thread, and then back in a background thread in the next await encountered, and so on (doesn't it?). This implies that the user can see some lag, since the UI Thread is continously interrupted by the continuation of the await to execute the next foreach cycle, and in that fraction of time the UI freezes a bit.

我想知道我是否最好像这样编写代码:

I want to know if I better write code like this:

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,即用户不会察觉任何滞后.

In this way, the whole foreach is executed on the secondary thread, without a continuous switch between the UI thread and the secondary one. This implies that the UI thread is free to update the View for the entire duration of the foreach (for example a spinner or a progress bar), that is, no lag is perceived by the user.

我是对的吗?还是我缺少有关一直向下同步"的内容?

Am I right? or am I missing something about "async all the way down"?

我不是在寻找基于意见的简单答案,而是在寻求有关异步/等待准则的解释,以寻求最佳解决方案.

I'm not looking for simple opinion-based answers, I'm looking for an explanation of async/await guidelines in case like that and for the best way to resolve it.

我已阅读这个问题却不一样.这个问题是关于在异步方法上选择单个await而不是单个Task.Run等待.这个问题是关于调用1000 await的后果以及由于线程之间不断切换而造成的资源开销.

I've read this question but it is not the same. THAT question is about the choice of a SINGLE await on an async method versus a single Task.Run await. THIS question is about the consequences of calling 1000 await and the resources' overhead due to the continuous switching among threads.

推荐答案

您的分析在很大程度上是正确的.您似乎高估了这将给UI线程带来的负担.它会被要求做的实际工作量很小,因此很有可能保持良好状态,但是您可能已经做了足够的工作而做不到,所以您正确地做到这一点是正确的有兴趣不对UI线程执行继续操作.

Your analysis is largely correct. You seem to overestimate the burden that this would place on the UI thread; the actual work that it would be asked to do is fairly small, so odds are that it would be able to keep up fine, but it's possible that you'd be doing enough that it couldn't, so you're right to be interested in not performing the continuations on the UI thread.

您当然缺少的是避免所有回叫UI线程的首选方法. await操作时,如果实际上不需要该方法的其余部分返回到原始上下文,则只需将ConfigureAwait(false)添加到正在等待的任务的末尾即可.这样可以防止继续操作在当前上下文(UI线程)中运行,而让继续操作在线程池线程中运行.

What you're missing of course is the preferred way of avoiding all of the call backs to the UI thread. When you await an operation, if you don't actually need the rest of the method to return back to the original context you can simply add ConfigureAwait(false) to the end of the task that you're awaiting. This will prevent the continuation from running in the current context (which is the UI thread) and instead let the continuation run in a thread pool thread.

使用ConfigureAwait(false)可以避免UI不必要地负责非UI工作,同时还可以避免您需要调度线程池线程来完成比其需要做的更多工作.

Using ConfigureAwait(false) allows you to avoid the UI being responsible for non-UI work unnecessarily while also preventing you from needing to schedule thread pool threads to do more work than they need to do.

当然,如果继续执行后最终要完成的工作实际上是要进行UI工作,则该方法不应该使用ConfigureAwait(false);,因为它实际上想要进行计划UI线程的延续.

Of course, if the work that you end up doing after your continuation is actually going to do UI work, then that method shouldn't be using ConfigureAwait(false);, because it actually wants to schedule the continuation on the UI thread.

这篇关于许多等待Async方法,或者一次等待包装Task.Run?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆