发布于 2026-01-06 1 阅读
0

异步/等待:我们其他人的指南 为什么需要异步?异步关键字 等待关键字 异步贯穿始终 结论 保持联系 规划您的软件开发职业生涯 由 Mux 呈现的 DEV 全球展示挑战赛:展示您的项目!

异步/等待对我们其他人来说

为什么选择异步?

异步关键字

等待关键词

全程异步

结论

保持联系

如何规划你的软件开发职业生涯

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

注:请参阅我博客上的原文

asyncC# 中的`and`到底是什么await?为什么 2018 年的 .Net 开发人员还需要了解它是什么以及如何使用它?

也许你已经用过了async/await,但发现自己不得不重新摸索一遍?没关系——我承认我也不是什么async/await专家。

我不得不通过惨痛的教训才明白,例如,使用ConfigureAwait(false).GetResult()(正如许多人可能建议的那样)并不能神奇地使你的异步方法“同步工作”。

async/await但这并不是对内部构造以及如何进行高级操作的深入研究async/await

这就是“面向大众的Async/await”“大众”指的是谁?

我们:

  • 可能从未用过async/await
  • 不是微软专家
  • 不是 C# 大师
  • async/await诚然,有时候我会忘记如何正确使用。
  • 可能得回谷歌一下,弄清楚为什么一个方法可以返回一个值,Task但却不能使用async关键字。
  • 可能会好奇为什么一个没有被标记为等待的方法async可以被其调用者等待。

我希望这篇文章是写给“我们其他人”的,它切中要点,实用性强。

PS:如果您想更深入地了解这个话题,我建议您从这篇文章开始。

为什么选择异步?

使用有什么好处async/await

面向网站开发人员

如果您使用 .Net 技术构建 Web 应用程序,答案很简单:可扩展性

当您进行 I/O 调用(数据库查询、文件读取、从 HTTP 请求读取数据等)时,处理当前 HTTP 请求的线程只是在等待

就是这样。它现在正等待操作系统返回结果。

例如,执行数据库查询最终会请求操作系统连接到数据库,发送消息并接收回复消息。但发出这些请求的是操作系统,而不是你的应用程序。

IO 操作需要时间。在这段时间里,等待线程(在你的应用程序中)可以用来做其他事情——特别是处理其他 HTTP 请求

使用此功能可以让你的 .Net Web 应用程序在等待 IO 完成的同时async/await处理更多 HTTP 请求。

面向桌面/应用程序开发人员

但是桌面应用程序无法处理HTTP请求……

桌面应用程序确实处理用户输入(键盘、鼠标等)、动画等。而且只有一个 UI 线程来执行这些操作。

用户输入是用户输入

如果我们认为 Web 应用中的 HTTP 请求只是用户输入,桌面(或应用)键盘和鼠标事件也只是用户输入——那么对于桌面/应用开发者来说,情况实际上更糟!他们只有一个线程来处理用户输入!

IO 问题和修复方法仍然适用。

然而,CPU密集型任务的问题是另一个需要关注的方面。简而言之,这类任务不应该在UI线程上执行。

任务类型包括:

  • 在循环中处理大量项目
  • 计算用于报告的汇总数据

如果你的应用在主线程/UI线程上执行此操作,那么就没有任何东西可以处理用户输入和UI相关内容,例如动画等。

这会导致应用程序卡顿和延迟。

解决方案是将 CPU 密集型任务卸载到后台任务/线程。这涉及到如何为新线程/任务排队,如何ConfigureAwait(false)在非 UI 上下文中维护代码的异步分支等等。所有这些都超出了本文的讨论范围。

异步关键字

让我们开始研究async/await关键词以及它们的使用方法。

人们对 async 关键字存在误解。为什么呢?因为它看起来好像能让你的方法变成异步的。但实际上并非如此。

这真让人困惑。这个async关键字并不能使我的方法变为异步的吗?是的。

那它接下来会做什么?

这个关键字的作用仅仅async启用 await 关键字。仅此而已。它没有其他任何作用。

所以,你就把async关键词看作关键词就好enableAwait

等待关键词

关键字await是关键所在。它本质上是在告诉代码的读者:

我(这个线程)会确保如果这里发生异步操作,我会去做其他事情(比如处理 HTTP 请求)。将来某个线程会在异步操作完成后回到这里。

一般来说,最常见的用途await是在进行 I/O 操作时,例如从数据库查询中获取结果或从文件中获取内容。

当你的await方法执行 I/O 操作时,实际执行 I/O 操作的并不是你的应用程序,而是操作系统。所以你的线程只是在那里等待……

await它会指示当前线程停止运行,去做一些有用的事情。让操作系统和 .NET 框架稍后在需要时再分配另一个线程。

请将此图作为视觉指南:

var result1 = await SomeAsyncIO1(); // OS is doing IO while thread will go do something else.
// A thread gets the results.

var result2 = await SomeAsyncIO2(result1); // Thread goes to do something else.
// One comes back...

await SomeAsyncIO3(result2); // Goes away again...
// Comes back to finish the method.
Enter fullscreen mode Exit fullscreen mode

此时你可能会问自己:

如果async关键字不能使方法异步化,那么什么才能使方法异步化?

那么,是什么让一个方法成为异步方法呢?

嗯——正如我们所了解到的,这并不是async关键词。真是不可思议。

任何返回“可等待对象”的方法——Task或者Task<T>可以使用await关键字等待的方法。

实际上还有其他类型的可等待对象。而且,“可等待”的方法并不一定非得是异步方法。不过先别管这些——这部分内容是写给“我们其他人”看的。

就本文而言,我们将假设“异步方法”是指返回 trueTask或 false的方法Task<T>

这种情况何时发生?

我们什么时候需要Task从方法中返回一个值呢?通常是在进行 I/O 操作时。大多数 I/O 库或 .NET 内置 API 都会提供方法的“异步”版本。

例如,该类SqlConnection有一个Open用于启动连接的方法。但是,它还有一个OpenAsync方法。它还有一个ExecuteNonQueryAsync方法。

public async Task<int> IssueSqlCommandAsync() {
    using(var con = new SqlConnection(_connectionString))
    {
        // Some code to create an sql command "sqlCommand" would be here...
        await con.OpenAsync();
        return await sqlCommand.ExecuteNonQueryAsync();
    }
}
Enter fullscreen mode Exit fullscreen mode

使 ` OpenAsyncand`ExecuteNonQueryAsync方法异步的不是关键字async`a`,而是它们返回 `a`Task或 `b`Task<T>

全程异步

可以这样做(注意缺少asyncand await):

public Task<int> GetSomeData() {
    return DoSomethingAsync();
}
Enter fullscreen mode Exit fullscreen mode

然后就是await这种方法:

// Inside some other method....
await GetSomeData();
Enter fullscreen mode Exit fullscreen mode

GetSomeData它不会等待调用DoSomethingAsync,而是直接返回Task。请记住,它await并不关心方法是否使用了async关键字,它只要求方法返回一个Task

这样做是可行的——创建一个调用异步方法但不等待的方法。

这是最佳实践

然而,这被认为是一种不良做法。为什么?

由于本文旨在简洁实用:

从头到尾使用 async/await 可以更好地捕获异步方法中的异常。

Task如果你用关键字标记每个返回 a 的方法async(这反过来又启用await关键字),它就能更好地处理异常,并且在查看异常消息和堆栈跟踪时使其更容易理解。

结论

简而言之:

  • async关键字并不会使方法异步——它只是启用该await关键字而已。

  • 如果一个方法返回一个 ` Taskor` Task<T>,那么可以使用关键字await来管理我们代码的异步细节。

  • 执行 I/O 操作总是会导致线程阻塞。这意味着您的 Web 应用程序无法并行处理足够多的 HTTP 请求,从而导致应用程序卡顿和延迟。

  • 使用此功能async/await可以帮助我们创建代码,使我们的线程在执行 I/O 操作时不再阻塞并执行有用的工作。

  • 这使得 Web 应用程序每秒可以处理更多请求,并且对用户响应速度更快。

我希望这篇介绍能够让大家理解async/await。这并非一个简单的话题——而且一如既往——随着时间的推移,通过使用这项功能积累经验将帮助我们理解其运作原理。

关于这一点,还有很多话要说,还有很多概念需要探讨async/await。其中包括:

  • 什么是SynchronizationContext?我应该在什么情况下注意它?
  • .Net Core 和 .Net Framework 有什么区别?我应该注意哪些方面?
  • 为什么我可以把一个方法标记为async void?这样做有什么作用?我应该这样做吗?
  • 如何将 CPU 密集型任务卸载到后台线程/任务中?
  • 是否可以在后台线程中执行任务,并在最后返回到 UI 线程?
  • 如何在同步代码中调用带有async关键字标记的方法?这样做会发生什么?

请告诉我你的想法——或者我是否遗漏了什么等等。谢谢!

保持联系

别忘了在推特领英上关注我

如何规划你的软件开发职业生涯

我会通过电子邮件简讯回答订阅者的问题,并就以下主题提供建议:

✔ 软件开发人员的职业发展阶段有哪些?
✔ 我如何知道自己处于哪个阶段?如何进入下一个阶段?
✔ 什么是技术领导者?如何成为一名技术领导者?

听起来很有趣?加入我们吧!

文章来源:https://dev.to/jamesmh/asyncawait-for-the-rest-of-us-c8a