System.Windows.Automation在枚举表格行方面非常缓慢,与UIAutomationCore相比。

8

我正尝试通过UI自动化(主要使用 TestStack.White 提供友好的界面; 它使用 System.Windows.Automation 作为后端)对我的应用程序进行自动化测试。 我有一个包含~200行的表格需要测试值(实际上我只想测试前几行和最后几行)。 我已经发现,仅使用COM-interop UIAutomationCore,可以在几秒钟内枚举行,但前提是我不使用White或 System.Windows.Automation 。 一旦 System.Windows.Automation 初始化,未来用于枚举行的UI自动化操作就会变慢:

First COM run: it took 0.04 seconds to get 102 rows!
First System.Windows.Automation run: it took 7.18 seconds to get 102 rows!
Second COM run: it took 7.87 seconds to get 102 rows!

我创建了一个简单的WinForms测试应用程序(TableTest.exe),以验证它与我的应用程序无关,而是与System.Windows.Automation有关:

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    var form = new Form() { Text = "TableTest", WindowState = FormWindowState.Maximized };
    var dgv = new DataGridView() { Name = "DGV", Dock = DockStyle.Fill, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill };
    dgv.Columns.Add("i", "i");
    dgv.Columns.Add("2i", "2i");
    dgv.Columns.Add("i^2", "i^2");
    dgv.Columns.Add("i^i", "i^i");
    for (int i = 0; i < 100; ++i)
        dgv.Rows.Add(i, i * 2, i * i, Math.Pow(i, i));
    form.Controls.Add(dgv);

    Application.Run(form);
}

然后我创建了另一个测试应用程序来测试第一个应用程序。它可以作为控制台应用程序或WinForms应用程序运行。首先我使用COM自动化进行测试,然后使用System.Windows.Automation进行测试,最后再次使用COM自动化进行测试。正如您从上面引述的输出中所看到的那样,第一个块执行非常快,接下来的两个块执行非常缓慢。如果我注释掉System.Windows.Automation代码块,那么两个COM块都会快速执行。

using UIA = Interop.UIAutomationCore;
static void Main(string[] args)
{
    var process = System.Diagnostics.Process.Start("TableTest.exe");
    System.Threading.Thread.Sleep(500);
    var uia = new UIA.CUIAutomation();
    var rootCom = uia.GetRootElement();
    var windowCom = rootCom.FindFirst(UIA.TreeScope.TreeScope_Children, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_NamePropertyId, "TableTest"));
    var dgvCom = windowCom.FindFirst(UIA.TreeScope.TreeScope_Descendants, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_AutomationIdPropertyId, "DGV"));
    var start = DateTime.Now;
    var rowCount = dgvCom.FindAll(UIA.TreeScope.TreeScope_Children, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_ControlTypePropertyId, UIA.UIA_ControlTypeIds.UIA_CustomControlTypeId)).Length;
    var elapsed = (DateTime.Now - start).TotalSeconds;
    Console.WriteLine(String.Format("It took {0} seconds to get {1} rows!", elapsed.ToString("f2"), rowCount));
    process.Kill();

    process = System.Diagnostics.Process.Start("TableTest.exe");
    System.Threading.Thread.Sleep(500);
    var root = AutomationElement.RootElement;
    var window = root.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "TableTest"));
    var dgv = window.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, "DGV"));
    start = DateTime.Now;
    rowCount = dgv.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom)).Count;
        elapsed = (DateTime.Now - start).TotalSeconds;
    Console.WriteLine(String.Format("It took {0} seconds to get {1} rows!", elapsed.ToString("f2"), rowCount));
        process.Kill();

    process = System.Diagnostics.Process.Start("TableTest.exe");
    System.Threading.Thread.Sleep(500);
    uia = new UIA.CUIAutomation();
    rootCom = uia.GetRootElement();
    windowCom = rootCom.FindFirst(UIA.TreeScope.TreeScope_Children, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_NamePropertyId, "TableTest"));
    dgvCom = windowCom.FindFirst(UIA.TreeScope.TreeScope_Descendants, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_AutomationIdPropertyId, "DGV"));
    start = DateTime.Now;
    rowCount = dgvCom.FindAll(UIA.TreeScope.TreeScope_Children, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_ControlTypePropertyId, UIA.UIA_ControlTypeIds.UIA_CustomControlTypeId)).Length;
    elapsed = (DateTime.Now - start).TotalSeconds;
    Console.WriteLine(String.Format("It took {0} seconds to get {1} rows!", elapsed.ToString("f2"), rowCount));
    process.Kill();
}

究竟是什么让 System.Windows.Automation 影响了 UI 自动化的性能?我查看了 White 的源代码,但没有发现明显的问题。我无法对 System.Windows.Automation 进行性能分析,因为找不到任何 PDB 文件。我对 UI 自动化不是很熟悉,也许对其他人来说很明显。White 版本是:0.13.0.0,我正在测试 64 位 Windows 7。


我重构了我的示例以使用 System.Windows.Automation,并发现问题出在那里,而不是在使用 COM 接口的 White 上。也就是说,当使用 System.Windows.Automation 时,第一行枚举与第二行一样慢。也许这有助于缩小问题范围? - Matt Chambers
我已经复制了您的问题,这非常奇怪。我理解为什么COM方法更快,因为它使用未管理的代码,可以更快地遍历树。但是,我不知道为什么System.Windows.Automation方法会杀死对COM库的后续调用。我不熟悉White,但是是否可以使用此包装器http://uiacomwrapper.codeplex.com/?这将为您提供White提供的接口,并具有COM方法的性能。 - Gary Wright
我对WinForms代码进行了一些检查,认为涉及到许多的SendMessages、PeekMessages和Waits。 - ralf.w.
我上次查看(几年前)时,System.Windows.Automation 在几乎所有方面都非常缓慢,因为它基于旧的纯托管 Vista 代码库。操作系统团队在 Windows 7 中完全重新实现了 UI Automation,大大提高了性能。 - Eric Brown
如果你正在使用System.Windows.Automation,那么你应该停止使用。据我所知,System.Windows.Automation不使用Windows 7及以上版本的本机UIA实现。 - Eric Brown
显示剩余5条评论
2个回答

4

我无法回答你的问题。但是许多人会从谷歌搜索“uiautomation slow”这个关键词而来到这里,而谷歌的第一个结果就是你的问题。(你写了一本畅销书)

对于所有从谷歌而来并且在处理缓慢的UIAutomation时遇到困难的人,我发布了这个答案。

System.Windows.Automation非常缓慢。在一个非常快的计算机上获取30个子元素可能需要1000毫秒!我甚至见过它在获取QT应用程序中树的子元素时永远挂起

除此之外,实现甚至不是线程安全的

System.Windows.Automation已被弃用。不要使用它!

MSDN中,你会发现以下注意事项:

UI自动化最初作为Microsoft .NET Framework的一部分在Windows XP中首次推出。尽管当时也发布了一个未管理的C++ API,但由于互操作性问题,客户端功能的实用性受到限制。对于Windows 7,API已经在组件对象模型(COM)中重写。虽然仍然记录了在较早版本的UI自动化中引入的库函数,但不应在新应用程序中使用。
解决慢性能的方法是使用新的IUIAutomationElement COM接口,而不是旧的System.Windows.Automation C#接口。之后,代码将运行得非常快!除此之外,新接口提供了更多的模式,而且Microsoft正在不断扩展它。在Windows 10 SDK(UIAutomationClient.h和UIAutomationCore.h)中添加了几个模式和属性,这些在.NET自动化框架中不可用。
以下模式在UIAutomation的COM版本中可用,而在System.Windows.Automation中不存在:
  • IUIAutomationLegacyIAccessiblePattern(遗留辅助功能模式)
  • IUIAutomationObjectModelPattern(对象模型模式)
  • IUIAutomationAnnotationPattern(注释模式)
  • IUIAutomationTextPattern2(文本模式2)
  • IUIAutomationStylesPattern(样式模式)
  • IUIAutomationSpreadsheetPattern(电子表格模式)
  • IUIAutomationSpreadsheetItemPattern(电子表格项模式)
  • IUIAutomationTransformPattern2(转换模式2)
  • IUIAutomationTextChildPattern(文本子模式)
  • IUIAutomationDragPattern(拖动模式)
  • IUIAutomationDropTargetPattern(放置目标模式)
  • IUIAutomationTextEditPattern(文本编辑模式)
  • IUIAutomationCustomNavigationPattern(自定义导航模式)

此外,还添加了以下控件类型:

  • AppBar(应用栏)
  • SemanticZoom(语义缩放)

此外,还添加了以下元素:

  • IUIAutomationElement2(元素2)
  • IUIAutomationElement3(元素3)
  • IUIAutomationElement4(元素4)

您写了一本畅销书,先生! - Fals

0

你发布的示例没有使用White... FWIW,White使用递归调用Automation.Find每次请求子节点。这样可以返回有效结果,但比从适当的父节点请求子树要慢 - 请注意,根节点从来不是请求子树的“适当”节点(参见MSDN上的注释)。由于你的示例只请求一次子节点,所以这不是问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接