System.Windows.Automation is very slow at enumerating table rows vs. UIAutomationCore
Asked Answered
H

2

9

I am trying to do automated testing of my application via UI Automation (mainly using TestStack.White to provide a friendly interface; it uses System.Windows.Automation as a back-end). I have a table with ~200 rows that I need to test the values of (actually I only want to test the first and last couple rows). I have discovered that using COM-interop UIAutomationCore by itself, I can enumerate the rows in a fraction of a second, but only when I don't use White or System.Windows.Automation. As soon as System.Windows.Automation initializes, future UI Automation actions to enumerate rows are slow:

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!

I created a simple WinForms test application (TableTest.exe to verify that it was System.Windows.Automation and not something to do with my application:

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);
}

Then I created another test app to test the first one. It works as either a console app or a WinForms app. First I test with COM automation, then with System.Windows.Automation, then again with COM automation. As you can see from the output I quoted above, the first block executes very quickly, the next two blocks execute excruciatingly slowly. If I comment out the System.Windows.Automation block code then both COM blocks execute quickly.

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();
}

What the heck is System.Windows.Automation doing that kills the performance of UI Automation? I've looked at the White source code and I don't see anything obvious. I can't profile System.Windows.Automation itself because I can't find any PDB for it. I'm not very familiar with UI Automation so maybe it'll be obvious to someone else. The White is: 0.13.0.0 and I'm testing on 64-bit Windows 7.

Hash answered 5/2, 2015 at 20:46 Comment(10)
I refactored my example to use System.Windows.Automation and discovered that the problem is there, not in White (which uses System.Windows.Automation rather than the COM interface). I.e. when using System.Windows.Automation, the first row enumeration is as slow as the second. Maybe that helps narrow things down?Hash
I've replicated your issue and it is very strange. I understand why the COM method is faster as it uses unmanaged code which will traverse the tree more quickly. Why the System.Windows.Automation method should kill subsequent calls to the COM libraries though I have no idea. I'm unfamiliar with White, but is it possible to use it with this wrapper uiacomwrapper.codeplex.com? This would give you the interface that White provides, with the performance of the COM method.Panelist
I did some inspection of the winforms-code and i think there are many many sendmessages, peekmessages and waits involvedLydgate
I ended up using an MSDN post to subclass DataGridView and make it implement the Automation interfaces in a more efficient way. I had to make some White subclasses too but that was easier.Hash
The last time I looked (several years ago) System.Windows.Automation was very slow at just about everything, since it was based on the old pure-managed Vista-era code base. The OS team completely reimplemented UI Automation in Windows 7, greatly improving performance.Haematosis
@EricBrown I'm having the same issue with very slow Table rows enumeration with UI Automation on Windows 7, so I guess the performance hasn't really improved.Anthropography
@Eric: I tested the same C# code using AutomationElement on Windows XP and on Windows 7 using the same computer, both running Framework 4.0. On Windows 7 it is faster - that is true - but only by factor 2. This is not "greatly improving performance" as you say. This is still EXTREMELY slow. To get into an acceptable performance range we would need an improvement by factor 100. The conclusion is that Microsoft has done a very crappy work with that UI Automation. And apart from the extreme slowness that shit is not even thread safe!Cinch
@Cinch If you're using System.Windows.Automation, you should stop. System.Windows.Automation (as far as I know) does not use the native UIA implementations for Windows 7 and above.Haematosis
In the mean time I found out the same. So what you wrote was imprecise. Microsoft did not improve System.Windows.Automation on Windows 7. It is still the same crap. They wrote a completely new framework based on COM, but they did not improve the UI Automation in the .NET framework. msdn.microsoft.com/en-us/library/windows/desktop/…Cinch
I changed the code to use the IUIAutomationElement COM interface instead of the System.Windows.Automation C# interface and now it is running LIGHTNING fast! Apart from that the new interface offers much more patterns and Microsoft is extending it continously. In the Windows 10 SDK several patterns and properties have been added.Cinch
C
5

I cannot answer your question. But many people will come here from Google who search for the keywords "uiautomation slow" and the first result in Google is your question. (You wrote a bestseller)

For all those coming from Google and struggling with slow UIAutomation I post this answer.

System.Windows.Automation is EXTREMELY slow. Obtaining 30 child elements may take 1000ms on a very fast computer! I have even seen it hanging forever while getting the child elements of a Tree in a QT application.

Apart from that the implementation is not even thread safe.

System.Windows.Automation is deprecated. Do not use it!

In the MSDN you find the following note:

UI Automation was first available in Windows XP as part of the Microsoft .NET Framework. Although an unmanaged C++ API was also published at that time, the usefulness of client functions was limited because of interoperability issues. For Windows 7, the API has been rewritten in the Component Object Model (COM). Although the library functions introduced in the earlier version of UI Automation are still documented, they should not be used in new applications.

The solution to slow performance is to use the new IUIAutomationElement COM interface instead of the old System.Windows.Automation C# interface. After that the code will be running lightning fast!

Apart from that the new interface offers much more patterns and Microsoft is extending it continously. In the Windows 10 SDK (UIAutomationClient.h and UIAutomationCore.h) several patterns and properties have been added which are not available in the .NET Automation framework.

The following patterns are available in the COM version of UIAutomation which do not exist in System.Windows.Automation:

  • IUIAutomationLegacyIAccessiblePattern
  • IUIAutomationObjectModelPattern
  • IUIAutomationAnnotationPattern
  • IUIAutomationTextPattern2
  • IUIAutomationStylesPattern
  • IUIAutomationSpreadsheetPattern
  • IUIAutomationSpreadsheetItemPattern
  • IUIAutomationTransformPattern2
  • IUIAutomationTextChildPattern
  • IUIAutomationDragPattern
  • IUIAutomationDropTargetPattern
  • IUIAutomationTextEditPattern
  • IUIAutomationCustomNavigationPattern

Additionally the following Control types have been added:

  • AppBar
  • SemanticZoom

Additionally the following Element's have been added:

  • IUIAutomationElement2
  • IUIAutomationElement3
  • IUIAutomationElement4
Cinch answered 20/1, 2017 at 16:5 Comment(1)
You wrote a bestseller Sir!Moldboard
V
0

The examples you posted do not use White... FWIW, White uses recursive calls to Automation.Find requesting the children each time. This returns valid results but is slower than requesting the subtree from the appropriate parent node - note that the root node is never the 'appropriate' node from which to request the subtree (see the note on MSDN). Since your example only requests the children once, that is not the issue.

Viscounty answered 29/7, 2015 at 16:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.