我们一直在使用MS UIA框架,并注意到在Windows 10 / .NET 4.6中查找对象集合时出现的显着减速。
在Windows 10 / .NET 4.6上测试AutomationElement.FindAll()时,查找元素集合的时间平均大约是在Windows 8.1 / .NET 4.5上查找完全相同的元素时的3到5倍.1盒子。 我的测试是针对虚拟化的WPF DataGrid(使用Recycling)并获取DataGrid每行内的所有单元格。
在我们的Win10框中,每个FindAll调用以获取每行中的单元格大约需要30到50毫秒甚至更长时间。在Win8.1盒子上大约需要5到10毫秒。我无法弄清楚原因,但我不认为问题仅限于DataGrid,因为我们的FindAll()调用没什么特别的。
//get grid
AutomationElement gridElement = AutomationElement.RootElement.FindFirst(TreeScope.Descendants,
new PropertyCondition(AutomationElement.AutomationIdProperty, "dataGridAutomationId"));
//get all visible rows
AutomationElementCollection dataItems = gridElement.FindAll(TreeScope.Descendants,
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.DataItem));
foreach (AutomationElement dataItem in dataItems)
{
if (!string.IsNullOrEmpty(dataItem.Current.Name))
{
//call under test
AutomationElementCollection cells = dataItem.FindAll(TreeScope.Children,
new PropertyCondition(AutomationElement.ClassNameProperty, "DataGridCell"));
}
}
AutomationElement.RootElement的使用仅用于测试。 'dataItem.FindAll()'调用是我正在测试的。
赢得8.1和Win10机器规格:
我们尝试通过com包装使用非托管MS uia API,并且在Win10上没有看到明显的性能改进。
非常感谢任何建议。
答案 0 :(得分:2)
这似乎已在最新一轮的Windows 10更新(kb/3093266)中得到修复。据一位MS支持代表说:
“UIA经常调用NtQuerySystemInformation,调用该API的性能经常不令人满意。他们对该特定代码路径进行了更改,不再调用该API,从而提高了整体性能。”
不幸的是,这是他们拥有的所有信息,所以我无法确定该电话究竟是什么造成了这个问题。
更新和测试后,两台机器的性能相同。
答案 1 :(得分:0)
一般情况下应避免使用TreeScope而非直系儿童。它不仅可以扼杀性能,而且可能只是从不结束(取决于下面的内容......)。
我建议您使用其他判别式(ControlType,Name等)优化搜索,或仅在您100%确定子树非常小或有限时才使用它。
顺便说一句,这通常是大多数自动记录工具无法实现UI自动化的原因,他们并不聪明,无法确定只有人类可以看到的良好AutomationProperty标准,在他们想要的背景下自动化。答案 2 :(得分:0)
我建议尝试使用与FindAll()不同的方法,例如使用TreeWalker。 UIAutomation实现往往会在不同的OS版本之间发生变化(至少就是我的感觉)(如果要检查版本,它们会被编译为UIAutomationCore.dll)。请注意,您可以检查最新.NET框架的托管代码。 TreeWalker的示例链接:http://referencesource.microsoft.com/#UIAutomationClient/System/Windows/Automation/TreeWalker.cs
例如,要使用TreeWalker检查直接孩子,您可以使用:
var walker = TreeWalker.RawViewWalker;
var current = walker.GetFirstChild(/* a parent automation element here*/);
while (current != null)
{
// use current (an automationelement) here, than go to next:
current = walker.GetNextSibling(current);
}
答案 3 :(得分:0)
这可能与您的特定情况无关,但有时在托管的.NET UIA API和本机Windows UIA API之间可能存在性能特征的差异。因此,如果可行,您可能需要考虑通过使用Windows UIA API来查看是否通过与之交互的UI获得更好的性能结果。
作为测试,我刚刚创建了一个应用程序,它提供了一个DataGrid,其中包含25行,每行有10个单元格。然后,我在下面编写了UIA客户端代码,以访问通过UIA公开的每个单元的名称。 (关于我如何使用C#代码调用本机Windows UIA API的一些注释位于http://blogs.msdn.com/b/winuiautomation/archive/2015/09/30/so-how-will-you-help-people-work-with-text-part-2-the-uia-client.aspx。)
我认为测试代码真正有趣的是,一旦我拥有了DataItems的父元素,我就可以通过单个跨进程调用访问我需要的所有数据。鉴于跨进程调用很慢,我想尽可能少地进行调用。
谢谢,
盖
IUIAutomationElement rootElement = uiAutomation.GetRootElement();
// The first few steps below find a DataGridRowsPresenter for the
// DataGrid we're interested in.
IUIAutomationElement dataGridRowsPresenter = null;
// We'll be setting up various UIA conditions and cache requests below.
int propertyIdControlType = 30003; // UIA_ControlTypePropertyId
int propertyIdName = 30005; // UIA_NamePropertyId
int propertyIdAutomationId = 30011; // UIA_AutomationIdPropertyId
int propertyIdClassName = 30012; // UIA_ClassNamePropertyId
int controlTypeIdDataItem = 50029; // UIA_DataItemControlTypeId
// Look for the test app presenting the DataGrid. For this test, assume there's
// only one such UIA element that'll be found, and the current language doesn't
// effect any of the searches below.
string testAppName = "Window1";
IUIAutomationCondition conditionTestAppName =
uiAutomation.CreatePropertyCondition(
propertyIdName, testAppName);
IUIAutomationElement testAppElement =
rootElement.FindFirst(
TreeScope.TreeScope_Children,
conditionTestAppName);
// Did we find the test app?
if (testAppElement != null)
{
// Next find the DataGrid. By looking at the UI with the Inspect SDK tool first,
// we can know exactly how the UIA hierarchy and properties are being exposed.
string dataGridAutomationId = "DataGrid_Standard";
IUIAutomationCondition conditionDataGridClassName =
uiAutomation.CreatePropertyCondition(
propertyIdAutomationId, dataGridAutomationId);
IUIAutomationElement dataGridElement =
testAppElement.FindFirst(
TreeScope.TreeScope_Children,
conditionDataGridClassName);
// Did we find the DataGrid?
if (dataGridElement != null)
{
// We could simply look for all DataItems that are descendents of the DataGrid.
// But we know exactly where the DataItems are, so get the element that's the
// parent of the DataItems. This means we can then get that element's children,
// and not ask UIA to search the whole descendent tree.
string dataGridRowsPresenterAutomationId = "PART_RowsPresenter";
IUIAutomationCondition conditionDataGridRowsPresenter =
uiAutomation.CreatePropertyCondition(
propertyIdAutomationId, dataGridRowsPresenterAutomationId);
dataGridRowsPresenter =
dataGridElement.FindFirst(
TreeScope.TreeScope_Children,
conditionDataGridRowsPresenter);
}
}
// Ok, did we find the element that's the parent of the DataItems?
if (dataGridRowsPresenter != null)
{
// Making cross-proc calls is slow, so try to reduce the number of cross-proc calls we
// make. In this test, we can find all the data we need in a single cross-proc call below.
// Create a condition to find elements whose control type is DataItem.
IUIAutomationCondition conditionRowsControlType =
uiAutomation.CreatePropertyCondition(
propertyIdControlType, controlTypeIdDataItem);
// Now say that all elements returned from the search should have their Names and
// ClassNames cached with them. This means that when we access the Name and ClassName
// properties later, we won't be making any cross-proc call at that time.
IUIAutomationCacheRequest cacheRequestDataItemName = uiAutomation.CreateCacheRequest();
cacheRequestDataItemName.AddProperty(propertyIdName);
cacheRequestDataItemName.AddProperty(propertyIdClassName);
// Say that we also want data from the children of the elements found to be cached
// beneath the call to find the DataItem elements. This means we can access the Names
// and ClassNames of all the DataItems' children, without making more cross-proc calls.
cacheRequestDataItemName.TreeScope =
TreeScope.TreeScope_Element | TreeScope.TreeScope_Children;
// For this test, say that we don't need a live reference to the DataItems after we've
// done the search. This is ok here, because the cached data is all we need. It means
// that we can't later get current data (ie not cached) from the DataItems returned.
cacheRequestDataItemName.AutomationElementMode =
AutomationElementMode.AutomationElementMode_None;
// Now get all the data we need, in a single cross-proc call.
IUIAutomationElementArray dataItems = dataGridRowsPresenter.FindAllBuildCache(
TreeScope.TreeScope_Children,
conditionRowsControlType,
cacheRequestDataItemName);
if (dataItems != null)
{
// For each DataItem found...
for (int idxDataItem = 0; idxDataItem < dataItems.Length; idxDataItem++)
{
IUIAutomationElement dataItem = dataItems.GetElement(idxDataItem);
// This test is only interested in DataItems with a Name.
string dataItemName = dataItem.CachedName;
if (!string.IsNullOrEmpty(dataItemName))
{
// Get all the direct children of the DataItem, that were cached
// during the search.
IUIAutomationElementArray elementArrayChildren =
dataItem.GetCachedChildren();
if (elementArrayChildren != null)
{
int cChildren = elementArrayChildren.Length;
// For each child of the DataItem...
for (int idxChild = 0; idxChild < cChildren; ++idxChild)
{
IUIAutomationElement elementChild =
elementArrayChildren.GetElement(idxChild);
if (elementChild != null)
{
// This test is only interested in the cells.
if (elementChild.CachedClassName == "DataGridCell")
{
string cellName = elementChild.CachedName;
// Do something useful with the cell name now...
}
}
}
}
}
}
}
}