2015-10-06 6 views
2

Мы работаем с базой MS UIA и заметили, что, как представляется, значительное замедление при поиске коллекций объектов в Windows 10/.NET 4.6.Проблема с производительностью AutomationElement.FindAll()

При тестировании AutomationElement.FindAll() в ящике Windows 10/.NET 4.6 наши времена в среднем примерно в 3-5 раз дольше находят коллекции элементов, чем при поиске одинаковых элементов на Windows 8.1/.NET 4.5.1. Мой тест против WPF DataGrid с виртуализацией (с использованием утилизации) и получение всех ячеек внутри каждой строки 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() - это то, что я тестирую.

Win 8.1 и Win10 машина спецификации:

  • Xeon W3670 3.20GHz процессор
  • 12GB баран
  • 64bit OS

Мы попытались использовать неуправляемый MS Uia API с помощью ком упаковки и не видел заметных улучшений производительности на Win10.

Любой совет будет очень благодарен.

+0

Вы сравнили структуру datagrid между win8.1 и win10 уже? (Например, с inspect.exe). Интересно, может ли win10 использовать для этого другую структуру? не уверен, что это возможно. – Hansa

+0

Согласно проверке структура datagrid идентична. – Jordan

ответ

2

Это, как представляется, были зафиксированы в последнем туре Windows 10 обновлений (kb/3093266). По словам представителя службы поддержки MS, я говорил с:

«UIA часто вызывало NtQuerySystemInformation, производительность вызова этого API часто не является удовлетворительной. Они внесли изменения в этот конкретный путь кода и больше не вызывают этот API, и что улучшили общую производительность ».

К сожалению, это была вся информация, которую они имели, поэтому я не могу точно определить, что вызвало эту проблему.

После обновления и тестирования производительность на обеих машинах одинакова.

0

TreeScope, кроме непосредственных детей, следует избегать в целом. Не только он может убить производительность, но может просто никогда конец (в зависимости от того, что лежит под ...).

Предлагаю вам уточнить ваш поиск, используя другие дискриминаторы (ControlType, Name и т. Д.), Или использовать его, только если вы на 100% уверены, что поддерево действительно мало или ограничено.

Кстати, это часто является причиной того, что большинство автоматических инструментов для рекордеров отстают от автоматизации пользовательского интерфейса, они недостаточно умны, чтобы определить хорошие критерии AutomationProperty, которые могут видеть только люди, в контексте того, что они хотят автоматизировать.

+0

, что не объясняет различия в производительности между win8.1 и win10, не так ли? – Hansa

+0

Являются ли деревья автоматизации одинаковыми для обеих систем? –

+0

Согласно Inspect, деревья одинаковы для datagrid на обеих системах. – Jordan

0

Я предлагаю попробовать другой метод, чем FindAll(), например, с помощью TreeWalker. Реализации UIAutomation имеют тенденцию меняться (по крайней мере, это то, что я чувствовал) между различными версиями ОС (они компилируются в 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); 
} 
+0

Спасибо за предложение. Я тоже пробовал этот подход и получал ту же проблему, что и ядро ​​Win10 значительно медленнее, чем окно Win8.1 (примерно столько же, сколько при использовании FindAll()) – Jordan

0

Это не может быть связано с вашей конкретной ситуации, но иногда может быть разница в производительности характеристик между управляемым API UIA .NET и собственным API UIA Windows. Поэтому, если это практично, вы можете захотеть увидеть, улучшаете ли вы лучшие результаты с пользовательским интерфейсом, с которым вы взаимодействуете, используя API UIA Windows.

В качестве теста я просто создал приложение, которое представило DataGrid с 25 строками в нем и 10 ячейками в каждой строке. Затем я написал код клиента UIA ниже, чтобы получить доступ к имени каждой ячейки, как показано через МАУ. (Некоторые примечания о том, как я использую код C# для собственного API UIA для Windows, находятся в 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 , Я могу получить доступ ко всем данным, которые мне нужны, с помощью одного межпроцессного вызова. Учитывая, что перекрестные вызовы происходят медленно, я хочу сделать как можно меньше.

Спасибо,

Guy

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... 
          } 
         } 
        } 
       } 
      } 
     } 
    } 
} 
+0

Мы попытались использовать неуправляемый api через com wrapping и не видел разницы в производительности. Я не уверен, что это отвечает на мой вопрос, поскольку он не объясняет, почему существует разница в производительности между Win8.1 и Win10. – Jordan

+0

Как в стороне, есть ли причина, по которой MS толкает родную uia api вместо управляемой? – Jordan

Смежные вопросы