诊断由扩展引起的 UI 延迟

当 UI 无响应时,Visual Studio 会检查 UI 线程的调用堆栈,从叶开始并面向基础。 如果 Visual Studio 确定调用堆栈帧属于已安装和已启用扩展的一部分的模块,则会显示通知。

UI delay (unresponsiveness) Notification

通知通知用户 UI 延迟(即 UI 中的无响应)可能是来自扩展的代码的结果。 它还为用户提供了用于禁用该扩展的扩展或将来通知的选项。

本文档介绍如何诊断扩展代码中导致 UI 延迟通知的内容。

注意

不要使用 Visual Studio 试验实例来诊断 UI 延迟。 使用实验实例时,UI 延迟通知所需的调用堆栈分析的某些部分将被关闭,这意味着可能不会显示 UI 延迟通知。

诊断过程的概述如下:

  1. 确定触发器方案。
  2. 重新启动 VS 并启用活动日志记录。
  3. 启动 ETW 跟踪。
  4. 触发通知以再次显示。
  5. 停止 ETW 跟踪。
  6. 检查活动日志以获取延迟 ID。
  7. 使用步骤 6 中的延迟 ID 分析 ETW 跟踪。

在以下部分中,我们将更详细地完成这些步骤。

确定触发器方案

若要诊断 UI 延迟,首先需要确定哪些(操作序列)会导致 Visual Studio 显示通知。 这是为了让你能够在以后启用日志记录的情况下触发通知。

使用活动日志记录重启 VS

Visual Studio 可以生成一个“活动日志”,在调试问题时提供有用的信息。 若要在 Visual Studio 中启用活动日志记录,请使用 /log 命令行选项打开 Visual Studio。 Visual Studio 启动后,活动日志将存储在以下位置:

%APPDATA%\Microsoft\VisualStudio\<vs_instance_id>\ActivityLog.xml

若要详细了解如何查找 VS 实例 ID,请参阅 用于检测和管理 Visual Studio 实例的工具。 稍后我们将使用此活动日志来了解有关 UI 延迟和相关通知的详细信息。

启动 ETW 跟踪

可以使用 PerfView 收集 ETW 跟踪。 PerfView 提供易于使用的接口,用于收集 ETW 跟踪和分析。 使用以下命令收集跟踪:

Perfview.exe collect C:\trace.etl /BufferSizeMB=1024 -CircularMB:2048 -Merge:true -Providers:*Microsoft-VisualStudio:@StacksEnabled=true -NoV2Rundown /kernelEvents=default+FileIOInit+ContextSwitch+Dispatcher

这将启用“Microsoft-VisualStudio”提供程序,该提供程序是 Visual Studio 用于与 UI 延迟通知相关的事件。 它还指定 PerfView 可用于生成线程时间堆栈视图的内核提供程序的关键字 (keyword)。

触发通知以再次显示

PerfView 启动跟踪集合后,可以使用触发器操作序列(从步骤 1 开始)再次显示通知。 显示通知后,可以停止 PerfView 的跟踪收集,以处理和生成输出跟踪文件。

停止 ETW 跟踪

若要停止跟踪收集,只需在 PerfView 窗口中使用 “停止收集 ”按钮。 停止跟踪收集后,PerfView 将自动处理 ETW 事件并生成输出跟踪文件。

检查活动日志以获取延迟 ID

如前 提及所述,可以在 %APPDATA%\Microsoft\VisualStudio<vs_instance_id>\ActivityLog.xml 中找到活动日志。 每次 Visual Studio 检测到扩展 UI 延迟时,都会将节点作为源写入活动日志 UIDelayNotifications 。 此节点包含有关 UI 延迟的四条信息:

  • UI 延迟 ID,一个唯一标识 VS 会话中的 UI 延迟的序列号
  • 会话 ID,用于从头到尾唯一标识 Visual Studio 会话
  • UI 延迟是否显示通知
  • 可能导致 UI 延迟的扩展
<entry>
  <record>271</record>
  <time>2018/02/03 12:02:52.867</time>
  <type>Information</type>
  <source>UIDelayNotifications</source>
  <description>A UI delay (Delay ID = 0) has been detected. (Session ID=16e49d4b-26c2-4247-ad1c-488edeb185e0; Blamed extension="UIDelayR2"; Notification shown? Yes.)</description>
</entry>

注意

并非所有 UI 延迟都会导致通知。 因此,应始终检查显示的通知?值来正确标识正确的 UI 延迟。

在活动日志中找到正确的 UI 延迟后,记下节点中指定的 UI 延迟 ID。 在下一步中,你将使用 ID 查找相应的 ETW 事件。

分析 ETW 跟踪

接下来,打开跟踪文件。 可以使用 PerfView 的同一实例或启动新实例并将窗口左上角的当前文件夹路径设置为跟踪文件的位置来执行此操作。

Setting the folder path in Perfview

然后,在左窗格中选择跟踪文件,然后通过 从右键单击或上下文菜单中选择“打开 ”将其打开。

注意

默认情况下,PerfView 输出 Zip 存档。 打开 trace.zip 时,它会自动解压缩存档并打开跟踪。 可以通过取消检查跟踪收集期间的 Zip 框来跳过此内容。 但是,如果计划跨不同计算机传输和使用跟踪,我们强烈建议取消检查 Zip 框。 如果没有此选项,Ngen 程序集所需的 PDB 将不会随跟踪一起,因此不会在目标计算机上解析来自 Ngen 程序集的符号。 (有关 Ngen 程序集的 PDB 的详细信息,请参阅 此博客文章

PerfView 可能需要几分钟才能处理和打开跟踪。 打开跟踪后,各种“视图”列表会显示在它下面。

PerfView trace summary view

我们将首先使用 “事件” 视图来获取 UI 延迟的时间范围:

  1. 通过选择Events跟踪下的节点并从右键单击或上下文菜单中选择“打开”来打开事件视图。
  2. 在左窗格中选择“Microsoft-VisualStudio/ExtensionUIUnresponsiveness” 。
  3. 按 Enter

应用所选内容,所有 ExtensionUIUnresponsiveness 事件都显示在右窗格中。

Selecting events in Events view

右窗格中的每一行对应于 UI 延迟。 该事件包括一个“延迟 ID”值,该值应与步骤 6 中活动日志中的延迟 ID 匹配。 由于 ExtensionUIUnresponsiveness 在 UI 延迟结束时触发,因此事件(大致)的时间戳将标记 UI 延迟的结束时间。 该事件还包含延迟的持续时间。 我们可以从结束时间戳中减去持续时间,以获取 UI 延迟启动时的时间戳。

Calculating the UI delay time-range

例如,在上一屏幕截图中,事件的时间戳为 12,125.679,延迟持续时间为 6,143.085(ms)。 因此,

  • 延迟开始时间为 12,125.679 - 6,143.085 = 5,982.594。
  • UI 延迟时间范围为 5,982.594 到 12,125.679。

有了时间范围后,我们可以关闭 “事件 ”视图并打开 “线程时间”(使用 StartStop 活动)堆栈 视图。 此视图特别方便,因为阻止 UI 线程的扩展通常只是等待其他线程或 I/O 绑定操作。 因此, 大多数情况下,CPU Stack 视图(即大多数情况的“转到”选项)可能不会捕获线程所花费的阻塞时间,因为它在此期间不使用 CPU。 线程 时间堆栈 通过正确显示阻塞的时间来解决此问题。

Thread Time (with StartStop Activities) Stacks node in PerfView summary view

打开 “线程时间堆栈” 视图时,选择 devenv 进程以开始分析。

Thread Time Stacks view for UI delay analysis

“线程时间堆栈” 视图中,在页面左上角,可以将时间范围设置为上一步中计算的值,然后按 Enter ,以便将堆栈调整为该时间范围。

注意

如果 Visual Studio 已打开后启动跟踪集合,则确定哪个线程是 UI(启动)线程可能会适得其反。 但是,UI(启动)线程堆栈上的第一个元素很可能始终是操作系统 DLL(ntdll.dll 和 kernel32.dll),后跟devenv!?之后msenv!?。 此序列可帮助标识 UI 线程。

Identifying the startup thread

还可以通过仅包括包含包中的模块的堆栈来进一步筛选此视图。

  • GroupPats 设置为空文本,以删除默认添加的任何分组。
  • 除现有进程筛选器外,将 IncPats 设置为包含程序集名称的一部分。 在这种情况下,它应该是 devenv;UIDelayR2

Setting GroupPath and IncPath in Thread Time Stacks view

PerfView 在“帮助”菜单下提供了详细的指导,可用于识别代码中的性能瓶颈。 此外,以下链接提供了有关如何利用 Visual Studio 线程 API 优化代码的详细信息:

还可以在此处使用新的 Visual Studio 静态分析器(NuGet 包)来编写高效扩展的最佳做法。 请参阅 VS SDK 分析器和线程分析器的列表。

注意

如果由于你无法控制的依赖项而无法解决无响应(例如,如果扩展必须在 UI 线程上调用同步 VS 服务),我们希望了解它。 如果你是 Visual Studio 合作伙伴计划的成员,可以通过提交开发人员支持请求与我们联系。 否则,请使用“报告问题”工具提交反馈并将其包含在 "Extension UI Delay Notifications" 游戏中。 此外,请包含分析的详细说明。