C# System.Windows.Automation与UIAutomationCore相比在枚举表行方面非常慢

C# System.Windows.Automation与UIAutomationCore相比在枚举表行方面非常慢,c#,winforms,ui-automation,white-framework,microsoft-ui-automation,C#,Winforms,Ui Automation,White Framework,Microsoft Ui Automation,我试图通过UI自动化对我的应用程序进行自动化测试(主要使用TestStack.White提供友好的界面;它使用System.Windows.Automation作为后端)。我有一个大约200行的表,我需要测试它的值(实际上我只想测试第一行和最后几行)。我发现单独使用COM interop UIAutomationCore,我可以在几分之一秒内枚举行,但只有在不使用White或System.Windows.Automation的情况下。只要System.Windows.Automation初始化,

我试图通过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);
}

什么是
System.Windows.Automation
这样做会破坏UI自动化的性能?我查看了白色的源代码,但没有看到任何明显的东西。我无法分析System.Windows.Automation本身,因为我找不到它的任何PDB。我对UI自动化不是很熟悉,所以可能对其他人来说很明显。白色是:
0.13.0.0
,我正在64位Windows 7上测试。

您发布的示例不使用白色…FWIW,白色使用对自动化的递归调用。每次查找请求的子节点。这将返回有效的结果,但比从相应的父节点请求子树的速度慢-请注意,根节点永远不会被删除请求子树的“适当”节点(请参见上的说明)。由于您的示例只请求子节点一次,因此这不是问题所在。

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

对于所有那些来自谷歌并在缓慢的自动化中挣扎的人,我发布了这个答案

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

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

System.Windows.Automation已弃用。请勿使用它

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

UI自动化最初是在Windows XP中作为 微软.NETFramework。虽然非托管C++ API也是 当时出版的《客户机功能》的实用性是有限的 由于互操作性问题。对于Windows 7,API已 在组件对象模型(COM)中重写。 尽管在早期版本的中引入了库函数 UI自动化仍有文档记录,不应在新的应用程序中使用 应用程序

降低性能的解决方案是使用新的IUIAutomationeElementCOM接口,而不是旧的System.Windows.Automation C#接口。之后,代码将以迅雷不及掩耳的速度运行

除此之外,新界面提供了更多的模式,Microsoft正在不断扩展它。在Windows 10 SDK(UIAutomationClient.h和UIAutomationCore.h)中,添加了一些在.NET Automation framework中不可用的模式和属性

以下模式在UIAutomation的COM版本中可用,但在System.Windows.Automation中不存在:

  • IUIAutomationLegacyIAccessiblePattern
  • IUIAutomationObjectModelPattern
  • IUIAutomationAnnotationPattern
  • IUIAutomationtextPattern 2
  • IUIAutomationStylesPattern
  • IUIAutomationsSpreadsheetPattern
  • IUIAutomationsSpreadsheetitempattern
  • IUIAutomationTransformPattern 2
  • IUIAutomationTextChildPattern
  • IUIAutomationDragPattern
  • IUIAutomationDropTargetPattern
  • IUIAutomationTextEditPattern
  • IUIAutomationCustomNavigationPattern
此外,还添加了以下控件类型:

  • AppBar
  • 语义空间
此外,还添加了以下元素:

  • IUIAutomationElement2
  • IUIAutomationElement3
  • IUIAutomationElement4

我重构了我的示例,使用
System.Windows.Automation
并发现问题存在,而不是白色的(使用
System.Windows.Automation
而不是COM接口)。例如,当使用
System.Windows.Automation
时,第一行枚举与第二行枚举一样慢。也许这有助于缩小范围?我已经复制了您的问题,这非常奇怪。我理解COM方法为什么更快,因为它使用非托管代码,可以更快地遍历树。为什么使用System.Windows.Automation方法应该终止对COM库的后续调用,尽管我不知道。我不熟悉White,但可以将其与此包装器一起使用吗?这将为您提供White提供的接口,以及COM方法的性能。我对winforms代码进行了一些检查,我认为有许多sendmessages、peekmessages和waits involvedI最终使用MSDN post来对DataGridView进行子类化,并使其以更高效的方式实现自动化接口。我也必须创建一些白色子类,但这更容易。上次我查看时(几年前)System.Windows.Automation几乎在所有方面都非常慢,因为它基于旧的纯托管Vista时代的代码库。操作系统团队在Windows 7中完全重新实现了UI自动化,大大提高了性能。先生,你写了一本畅销书!
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();
}