以下内容来自Terry Mahaffey和Ramkumar Ramesh。
在Visual Studio 2019 v16.8中,我们在一篇文章中宣布了对协程的支持。从那个时候开始,我们引入了一些列和协程相关的新特性和改进。今天的这篇文章,我们将这些新东西来一个汇总给大家看看,所有这些新东西都已经在Visual Studio 2019 v16.11版本中可用。
从VS2019 v16.9版开始,单步进入(Step into)调试到一个协程的时候,执行流会直接进入到协程代码体中(有个一个特殊情况,即这个协程一开始就被设置成了暂停执行状态,在这种特殊情况下,执行流会直接跳过”Step over”)。跳过一个co_await将进入co_await后面的逻辑语句中,用于协程,这可能在一个完全不同的执行上下文中(甚至另一个线程)!
这允许单步执行协程,实现无缝匹配应用程序的逻辑流并跳过中间的实现细节。为获得最佳调试体验,应将协程状态的实现细节标记为非用户代码。单步执行协程现在还会在本地变量”Locals”窗口中按预期显示函数参数,以便你可以查看应用程序的状态,类似于单步执行同步函数。
我们通过对标准协程的调试可视化工具的一些改进,现在可以更轻松地查看已暂停执行协程的状态。 之前旧版本的coroutine_handle可视化器可以显示初始和最终暂停点的特殊指标,但仅显示其他暂停点的数字。这个数字并不总是很容易映射回原始协程中的特定点。可视化器还显示了协程的名称,但只是作为由实现生成的修改后的内部名称,没有对应的签名信息。如下图所示:
我们可以看到,在上图中,协程的名称被显示为:”sample_coroutine$_ResumeCoro$1(void)”。
使用Visual Studio 2019 v16.10中引入的新协程句柄可视化器,函数名称可以被正确地显示了。并且包含了完整的签名信息以帮助区分重载的协程。除了初始和最终挂起之外的挂起点信息,可视化器中还包括了源代码行号,以便于查找,如下图所示:
在上图中,我们可以看到,现在的协程被显示为”sample_coroutine(int)”,并标识其当前状态为已挂起状态,位于源代码的第35行。
在之前的一篇文章中,我们提到Legacy模式的await存在一些问题,以及在/std:c++latest中保持 /await开关与C++20协程支持不同的基本原理。Legacy模式对于早期采用C++协程但不是标准协程的用户很有用。
/await这个编译器开关不仅早于我们的/std:c++latest和/std:c++20这两个开关,还早于 /std:c++17。早在协程成为C++标准的一部分之前,一些开发者就开始使用协程了。这些开发者可以使用协程,而不要求他们的代码符合C++20甚至是C++17标准。由于标准协程仅在C++20和最新模式下可用,协程的早期采用者无法将其代码移动到更新的语言版本,因此被困在等待协程的遗留实现中。他们无法利用一些新功能,例如对称传输和改进的调试器支持,即使他们愿意对协程本身进行代码修改以使其符合C++20标准。
从Visual Studio 2019 v16.10版开始,我们引入了一个新开关,以帮助早期协程采用者过渡到合规协程并使用标准协程中的所有可用功能,这个开关就是:/await:strict。
使用此开关代替/await可以启用与标准模式相同的C++20协程支持,但没有/std:c++20的所有其他要求。这包括对所有标准C++20协程功能和调试器集成的支持,并禁用/await下仍然支持的所有遗留扩展/std:c++20协程和/await:strict之间的唯一区别是:后者没有为std::coroutine_handle 定义飞船操作符(spaceship operator)。不同的是,它定义了单独的关系运算符。
如果你的代码依赖于C++20中未采用的扩展,则从/await迁移到/await:strict可能需要修改源代码。与标准模式一样,它使用头文件头和std命名空间,因此你的代码将为C++20做好准备。使用/await:strict编译的代码使用与/std:c++latest相同的协程ABI,因此协程对象在两种模式之间可以保持兼容。
我们鼓励/await的所有用户迁移到/await:strict。 你可以利用所有新的协程功能,并确保协程代码在你可以移动到正式支持协程的C++语言版本时为C++20做好准备。 我们希望在未来的某个时候弃用和彻底地删除/await开关。
Visual Studio 2019 v16.11版本中还包括几个重要的修复程序,以提高协程的稳定性和可靠性。
最大的变化与优化器如何进行所谓的”提升”有关,这是一种决定哪些变量被放置在协程框架上以及哪些变量保留在(传统)堆栈上的算法。许多协程错误可以追溯到此处的错误决策。通常,这表现为崩溃,或者在协程恢复执行后变量具有不正确或随机的值。此算法已被重写为更准确,结果是更少的崩溃和更小的协程帧大小。旧算法仍然可以通过将/d2CoroNewPromotion-传递给cl.exe来访问。
一个相关的修复涉及如何存储异常对象。异常的生命周期规则可能会变得复杂,需要在决定变量提升时专门处理它们。
发现并修复了与协程中的catch块相关的错误。在某些情况下(即,当try块中唯一的抛出调用来自用户定义的awaiter方法时),优化器可能会错误地断定catch块已死,并错误地将其删除。编译器现在知道awaiter方法可以抛出。
最后,解决了一个与调用析构函数的方式和时间有关的严重问题。这涉及如何在协程中跟踪某些对象的构造状态,这些对象在离开作用域时有条件地销毁。在使用条件(三元)运算符构造对象时,它最常出现。该错误表现为此类临时对象的析构函数未被调用,或者在某些情况下被调用两次。这也已在 v16.11版本中修复。
我要是有空,我就试试这个新的开关:/await:strict。异步操作确实比较吸引我。
Microsoft Visual C++团队的博客是我非常喜欢的博客之一,里面有很多关于Visual C++的知识和最新开发进展。大浪淘沙,如果你对Visual C++这门古老的技术还是那么感兴趣,则可以经常去他们那(或者我这)逛逛。
本文来自:《C++20 Coroutine Improvements in Visual Studio 2019 version 16.11》
正如你们所知道的,拓扑梅尔智慧办公平台(Topomel Box)是一款绿色软件,主要面向经常使用电脑的朋友。它提供了各种提升办公效率的小功能,同时操作上尽可能地简单方便。
我想:你值得拥有。