【问题标题】:System.Windows.Automation is very slow at enumerating table rows vs. UIAutomationCoreSystem.Windows.Automation 在枚举表行与 UIAutomationCore 方面非常慢
【发布时间】:2015-04-05 21:08:50
【问题描述】:

我正在尝试通过 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 自动化不是很熟悉,所以也许对其他人来说很明显。白色是:0.13.0.0,我正在 64 位 Windows 7 上进行测试。

【问题讨论】:

  • 我重构了我的示例以使用 System.Windows.Automation 并发现问题存在,而不是 White(它使用 System.Windows.Automation 而不是 COM 接口)。 IE。使用System.Windows.Automation 时,第一行枚举与第二行一样慢。也许这有助于缩小范围?
  • 我已经复制了您的问题,这很奇怪。我理解为什么 COM 方法更快,因为它使用可以更快地遍历树的非托管代码。尽管我不知道为什么 System.Windows.Automation 方法应该终止对 COM 库的后续调用。我对 White 不熟悉,但是可以将它与这个包装器 uiacomwrapper.codeplex.com 一起使用吗?这将为您提供 White 提供的接口,以及 COM 方法的性能。
  • 我对 winforms-code 做了一些检查,我认为有很多 sendmessages、peekmessages 和等待
  • 我最终使用 MSDN 帖子来继承 DataGridView 并使其以更有效的方式实现自动化接口。我也必须制作一些 White 子类,但这更容易。
  • 我最后一次查看(几年前) System.Windows.Automation 在几乎所有方面都非常缓慢,因为它基于旧的纯托管 Vista 时代代码库。操作系统团队在 Windows 7 中完全重新实现了 UI 自动化,大大提高了性能。

标签: c# winforms ui-automation white-framework microsoft-ui-automation


【解决方案1】:

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

对于所有来自 Google 并在缓慢的 UIAutomation 中苦苦挣扎的人,我发布了这个答案。

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

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

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

MSDN 中,您可以找到以下注释:

UI 自动化最初是在 Windows XP 中作为 微软 .NET 框架。虽然非托管 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
  • IUIAutomationStylesPattern
  • IUIAutomation 电子表格模式
  • IUIAutomationSpreadsheetItemPattern
  • IUIAutomationTransformPattern2
  • IUIAutomationTextChildPattern
  • IUIAutomationDragPattern
  • IUIAutomationDropTargetPattern
  • IUIAutomationTextEditPattern
  • IUIAutomationCustomNavigationPattern

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

  • AppBar
  • 语义缩放

另外还添加了以下元素:

  • IUIAutomationElement2
  • IUIAutomationElement3
  • IUIAutomationElement4

【讨论】:

  • 你写了一本畅销书先生!
【解决方案2】:

您发布的示例不使用 White... FWIW,White 使用递归调用 Automation.Find 每次都请求子级。这会返回有效结果,但比从适当的父节点请求子树要慢 - 请注意,根节点永远不是从中请求子树的“适当”节点(请参阅MSDN 上的注释)。由于您的示例只请求孩子一次,这不是问题。

【讨论】:

    猜你喜欢
    • 2017-06-05
    • 1970-01-01
    • 2014-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多