假设我们必须通过异步流程在数据库中写入包含 1000 个元素的列表。是等待 1000 次异步插入语句更好,还是将所有 1000 次插入包装在一个封装到 Task.Run
中的单个同步方法中?声明,等待一次?
例如,SqlCommand
每个方法都与他的async
相结合版本。在本例中,我们有一个插入语句,因此我们可以调用 ExecuteNonQuery
或ExecuteNonQueryAsync
.
通常,在异步/等待指南中,我们读到,如果您有可用于某些方法的异步版本,则应该使用它。所以假设我们写:
async Task Save(IEnumerable<Savable> savables)
{
foreach(var savable in savables)
{
//create SqlCommand somehow
var sqlCmd = CreateSqlCommand(savable);
//use asynchronous version
await sqlCmd.ExecuteNonQueryAsync();
}
}
这段代码很清楚。然而,每次它从await部分出去时,它也会在UI线程上返回,然后在遇到的下一个await中返回到后台线程,依此类推(不是吗?)。这意味着用户可能会看到一些延迟,因为 UI 线程不断被 await
的继续中断。执行下一个foreach
循环,在这段时间里,用户界面有点卡住。
我想知道我是否可以更好地编写这样的代码:
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 。 (例如旋转器或进度条),也就是说,用户不会感觉到任何延迟。
我说得对吗?或者我错过了一些关于“异步一直下来”的事情?
我不是在寻找简单的基于意见的答案,而是在寻找异步/等待准则的解释(以防出现这种情况)以及解决该问题的最佳方法。
编辑:
我已阅读 this question但它不一样。这个问题是关于选择一个单await
异步方法与单个方法 Task.Run
等待。这个问题是关于调用 1000 await
的后果以及线程间不断切换带来的资源开销。
最佳答案
您的分析基本上是正确的。您似乎高估了这会给 UI 线程带来的负担;要求它做的实际工作相当小,所以它很可能能够保持良好状态,但有可能你已经做了足够多的事情而它却做不到,所以你这样做是对的不想在 UI 线程上执行延续。
当然,您缺少的是避免所有回调 UI 线程的首选方法。当您等待
某个操作时,如果您实际上不需要方法的其余部分返回原始上下文,您只需将 ConfigureAwait(false)
添加到末尾即可您正在等待的任务。这将阻止延续在当前上下文(即 UI 线程)中运行,而是让延续在线程池线程中运行。
使用 ConfigureAwait(false)
可以避免 UI 不必要地负责非 UI 工作,同时还可以防止您需要调度线程池线程来完成超出其需要的工作。
当然,如果您在继续之后最终完成的工作实际上是要执行 UI 工作,那么该方法不应该使用 ConfigureAwait(false);
,因为它实际上 < em>想要在 UI 线程上安排延续。
关于c# - 许多等待异步方法,还是单个等待包装 Task.Run?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40530737/