2015-09-02 12 views
9

Мы используем среду UIAutomation Microsoft для разработки клиента, который отслеживает события конкретного приложения и реагирует на них по-разному. Мы начали с управляемой версии фреймворка, но из-за проблем с задержкой переместились в родную версию, завернутую в UIACOMWrapper. После дополнительных проблем с производительностью внутри нашего (массивного) WPF-приложения мы решили перенести его в отдельное приложение терминала (перенос событий в наше приложение WPF через UDP), который, казалось, исправить все проблемы с производительностью. Единственная проблема заключается в том, что каждые несколько минут события для TabSelection, StructureChanged, WindowOpened и WindowClosed перестают захватываться в течение нескольких минут. Неожиданно события PropertyChanged все еще принимаются и обрабатываются, когда это происходит. Я отправлю соответствующий код нашего монитора событий, но это, вероятно, не имеет значения, поскольку мы видели подобное поведение при использовании собственной утилиты AccEvent от Microsoft. Я не могу опубликовать код контролируемого приложения, так как он является конфиденциальным и конфиденциальным, я могу сказать, что это приложение WinForms, в котором размещены окна WPF, а также довольно массивные. Кто-нибудь видел такое поведение при работе с инфраструктурой автоматизации пользовательского интерфейса? Спасибо за ваше время.События автоматизации пользовательского интерфейса перестают приниматься после мониторинга приложения и затем через некоторое время перезапускаются

Вот код монитор (я знаю, что обработка событий на резьбе UI Automation здесь, но сдвинув его к выделенной теме ничего не меняет):

 public void registerHandlers() 
    { 
     //Register on structure changed and window opened events 
     System.Windows.Automation.Automation.AddStructureChangedEventHandler(
      this.getMsAutomationElement(), System.Windows.Automation.TreeScope.Subtree, this.handleStructureChanged); 
     System.Windows.Automation.Automation.AddAutomationEventHandler(
      System.Windows.Automation.WindowPattern.WindowOpenedEvent, 
      this.getMsAutomationElement(), 
      System.Windows.Automation.TreeScope.Subtree, 
      this.handleWindowOpened); 
     System.Windows.Automation.Automation.AddAutomationEventHandler(
      System.Windows.Automation.WindowPattern.WindowClosedEvent, 
      System.Windows.Automation.AutomationElement.RootElement, 
      System.Windows.Automation.TreeScope.Subtree, 
      this.handleWindowClosed); 

     this.registerValueChanged(); 
     this.registerTextNameChange(); 
     this.registerTabSelected(); 
     this.registerRangeValueChanged(); 
    } 

    private void registerRangeValueChanged() 
    { 
     if (this.getMsAutomationElement() != null) 
     { 
      System.Windows.Automation.Automation.AddAutomationPropertyChangedEventHandler(
        this.getMsAutomationElement(), 
        System.Windows.Automation.TreeScope.Subtree, this.handlePropertyChange, 
        System.Windows.Automation.RangeValuePattern.ValueProperty); 
     } 
    } 

    private void unregisterRangeValueChanged() 
    { 
     System.Windows.Automation.Automation.RemoveAutomationPropertyChangedEventHandler(
       this.getMsAutomationElement(), 
       this.handlePropertyChange); 
    } 

    private void registerValueChanged() 
    { 
     if (this.getMsAutomationElement() != null) 
     { 
      System.Windows.Automation.Automation.AddAutomationPropertyChangedEventHandler(
       this.getMsAutomationElement(), 
       System.Windows.Automation.TreeScope.Subtree, this.handlePropertyChange, 
       System.Windows.Automation.ValuePattern.ValueProperty); 
     } 
    } 

    private void unregisterValueChanged() 
    { 
     System.Windows.Automation.Automation.RemoveAutomationPropertyChangedEventHandler(
          this.getMsAutomationElement(), 
          this.handlePropertyChange); 
    } 

    private void registerTextNameChange() 
    { 
     if (this.getMsAutomationElement() != null) 
     { 
      System.Windows.Automation.Automation.AddAutomationPropertyChangedEventHandler(
      this.getMsAutomationElement(), 
      System.Windows.Automation.TreeScope.Subtree, this.handlePropertyChange, 
       System.Windows.Automation.AutomationElement.NameProperty); 
     } 
    } 

    private void unregisterTextNameChange() 
    { 
     System.Windows.Automation.Automation.RemoveAutomationPropertyChangedEventHandler(
     this.getMsAutomationElement(), 
     this.handlePropertyChange); 
    } 
    private void handleWindowOpened(object src, System.Windows.Automation.AutomationEventArgs e) 
    { 
     Console.ForegroundColor = ConsoleColor.Magenta; 
     Console.WriteLine(DateTime.Now.ToShortTimeString() + " " + "Window opened:" + " " + 
      (src as System.Windows.Automation.AutomationElement).Current.Name); 

     System.Windows.Automation.AutomationElement element = src as System.Windows.Automation.AutomationElement; 
     //this.sendEventToPluginQueue(src, e, element.GetRuntimeId(), this.getAutomationParent(element).GetRuntimeId()); 
     //Fill out the fields of the control added message 
     int[] parentId = this.getAutomationParent(element).GetRuntimeId(); 
     this.copyToIcdArray(parentId, 
      this.protocol.getMessageSet().outgoing.ControlAddedMessage.Data.controlAdded.parentRuntimeId); 
     this.copyToIcdArray(element.GetRuntimeId(), 
      this.protocol.getMessageSet().outgoing.ControlAddedMessage.Data.controlAdded.runtimeId); 
     //Send the message using the protocol 
     this.protocol.send(this.protocol.getMessageSet().outgoing.ControlAddedMessage); 
    } 

    private void copyToIcdArray(int[] runtimeId, ICD.UI_AUTOMATION.RuntimeId icdRuntimeId) 
    { 
     icdRuntimeId.runtimeIdNumberOfItems.setVal((byte)runtimeId.Count()); 
     for (int i = 0; i < runtimeId.Count(); i++) 
     { 
      icdRuntimeId.runtimeIdArray.getElement(i).setVal(runtimeId[i]); 
     } 
    } 

    private void handleWindowClosed(object src, System.Windows.Automation.AutomationEventArgs e) 
    { 
     if (src != null) 
     { 
      Console.ForegroundColor = ConsoleColor.Cyan; 
      Console.WriteLine(DateTime.Now.ToShortTimeString() + " " + "Window closed:" + " " + 
       (src as System.Windows.Automation.AutomationElement).GetRuntimeId().ToString()); 

      System.Windows.Automation.AutomationElement element = src as System.Windows.Automation.AutomationElement; 
      this.copyToIcdArray(element.GetRuntimeId(), 
       this.protocol.getMessageSet().outgoing.ControlRemovedMessage.Data.controlRemoved.runtimeId); 
      //Send the message using the protocol 
      this.protocol.send(this.protocol.getMessageSet().outgoing.ControlRemovedMessage); 

      //this.sendEventToPluginQueue(src, e, element.GetRuntimeId()); 
     } 
    } 

EDIT: Я забыл упомянуть, что я сильно подозревают, что проблема в том, что один из потоков обработчика событий UI-Automation застрял как-то. Причина, по которой я верю в это, заключается в том, что когда проблема возникла на моем мониторе, я начал экземпляр AccEvent и получил все отсутствующие события, которые мой монитор не получал. Это означает, что события запускаются, но не передаются на мой монитор.

EDIT2: Я забыл упомянуть, что это происходит в Windows 8 с конкретным целевым приложением, я не видел этого явления на своей машине Windows 7 с другими приложениями. Еще одна интересная вещь заключается в том, что это происходит периодически более или менее, но независимо от того, когда я подписываюсь на события, т. Е. Это может произойти почти сразу после подписки, но затем требуется несколько минут для повторной регистрации.

+0

Не имея рамки UIAutomation знания, я хочу высказать некоторые очевидные вещи: Вы проверить eventlogs из машин, участвующих (тайм-ауты, stackoverflows и т.д.). Вы просматривали ошибки привязки WPF во время выполнения (например, в области вывода в VS (включить трассировку WPF), совпадают ли они с отсутствующими событиями? –

+0

Можете ли вы добавить [logging] (https://msdn.microsoft.com/en -us/library/windows/desktop/jj160543 (v = vs.85) .aspx) к событию, которое вы подозреваете. Как долго длится каждый тест для запуска примерно? 7 минут точно или переменно на минуту или две? у него есть возможность делать скриншоты? Это машина, посвященная автоматизации ui? – lloyd

+0

@o_weisman любой шанс, что у вас есть понимание на http://stackoverflow.com/questions/32540442/when-i-try-to-use-ui- 0: – tofutim

ответ

2

Я видел это поведение в своем проекте. Решение было отписано и повторно подписано на события с использованием таймера. Кроме того, я запустил любое действие после событий в новой задаче (запущен в пуле потоков STA).

+0

Спасибо за совет, мы рассмотрели возможность сделать то, что вы предлагаете (отписаться, подписаться), но это означает, что наш мониторинг может пропустить некоторые из событий, когда он выполняет эту переподписку, что делает весь тестовый сценарий не на 100% надежным. Почему вы используете поток STA, когда в документации четко указано, что вы должны использовать потоки MTA и, в частности, использовать тот же самый для подписки \ отписки? Вы видели какую-либо разницу, используя этот метод? –

+0

1) Я использую поток STA для выполнения действий обработчиков, чтобы не выполнять подписку. Таким образом, я могу убедиться, что он внутренне синхронизирован, я не думаю, что это необходимо, поэтому скобка. вы все равно можете создавать другие потоки из своего действия. – DoronG

+0

2) вы можете подписаться, прежде чем отказаться от подписки. Пока обработчик является новым объектом. Вы можете создать новый делегат действия, который обертывает тот же метод. – DoronG

2

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

Все, что я говорю ниже, относится к родному МАУ API в ОС Windows , а не управляемый API UIA .NET. Все улучшения в МАУ за последние годы были внесены в API UIA Windows. Поэтому всякий раз, когда я пишу код UIA клиента C#, я вызываю МАУ через управляемую оболочку, которую я генерирую с помощью инструмента tlbimp.exe SDK.

То есть, я сначала создать оболочку с помощью команды ...

«C: \ Program Files (x86) \ Microsoft SDKs \ Windows \ v8.1A \ Bin \ NETFX 4.5.1 Tools \ x64 \ tlbimp.exe "c: \ windows \ system32 \ uiautomationcore.dll /out:Interop.UIAutomationCore.dll

Затем включите ссылку на файл Interop.UIAutomationCore.dll в моем проекте C#, добавьте« using Interop. UIAutomationCore;» к моему файлу C#, а затем я могу делать такие вещи, как ...

IUIAutomation uiAutomation = new CUIAutomation8(); 

IUIAutomationElement rootElement = uiAutomation.GetRootElement(); 

uiAutomation.AddAutomationEventHandler(
    20016, // UIA_Window_WindowOpenedEventId 
    rootElement, 
    TreeScope.TreeScope_Descendants, 
    null, 
    this); 

...

public void HandleAutomationEvent(IUIAutomationElement sender, int eventId) 
{ 
    // Got a window opened event... 
} 

В Windows 7 были некоторые важные ограничения в обработчиках событий UIA. Легко было писать обработчики событий, которые не учитывали эти ограничения, и это может привести к длительным задержкам при взаимодействии с МАУ. Например, было важно не добавлять или удалять обработчик событий UIA внутри обработчика событий. Поэтому в то время я намеренно не вызывал никаких вызовов UIA изнутри обработчиков событий. Вместо этого я отправляю сообщение или добавляю какое-то действие в очередь, позволяю обработчику событий возвращаться и предпринимаю любые действия, которые я хотел бы ответить в ответ на событие вскоре после этого в другом потоке. Это потребовало некоторой дополнительной работы с моей стороны, но я не хотел рисковать сдерживать задержки. И любые созданные мной потоки будут запущены в MTA.

Пример описанного выше действия находится в моем старом образце отслеживания фокусировки вверх по https://code.msdn.microsoft.com/windowsapps/Windows-7-UI-Automation-6390614a/sourcecode?fileId=21469&pathId=715901329. Файл FocusEventHandler.cs создает поток MTA и отправляет сообщения в очередь, чтобы избежать вызовов МАУ внутри обработчика событий.

Начиная с окна 7, я знаю, что ограничения в МАУ, связанные с потоками и задержками, были ослаблены, и вероятность встречных задержек была уменьшена. Совсем недавно произошли некоторые улучшения между Windows 8.1 и Windows 10 в этой области, поэтому, если бы было удобно запускать ваш код в Windows 10, было бы интересно посмотреть, сохраняются ли там задержки.

Я знаю, что это занимает много времени, но вам может быть интересно удалить взаимодействие с МАУ внутри ваших обработчиков событий и посмотреть, не задерживаются ли задержки. Если это так, то это будет случай определения того, какое действие, похоже, вызывает проблему, и выяснение, есть ли альтернативный способ достижения ваших целей без взаимодействия с МАУ в обработчиках событий.

Например, в обработчике события, вы звоните ...

this.getAutomationParent (элемент) .GetRuntimeId();

Я ожидаю, что это приведет к двум обратным вызовам в приложении-провайдере, которое сгенерировало событие. Первый вызов - получить родительский элемент исходного элемента, а второй вызов - получить RuntimeId этого родителя. Поэтому, пока МАУ ждет, пока ваш обработчик событий вернется, вы дважды позвонили в МАУ. Хотя я не знаю, что это проблема, я бы избегал этого.

Иногда вы можете избежать перекрестного обращения к процессу провайдера, имея некоторые данные, представляющие интерес, с кешем с самим событием. Например, скажем, я знаю, что мне захочется RuntimeId элемента, который вызвал событие WindowOpened. Я могу попросить МАУ кэшировать эти данные с событиями, которые я получаю, когда я регистрируюсь для событий.

int propertyRuntimeId = 30000; // UIA_RuntimeIdPropertyId 

...

IUIAutomationCacheRequest cacheRequestRuntimeId = uiAutomation.CreateCacheRequest(); 
cacheRequestRuntimeId.AddProperty(propertyRuntimeId); 

uiAutomation.AddAutomationEventHandler(
    20016, // UIA_Window_WindowOpenedEventId 
    rootElement, 
    TreeScope.TreeScope_Descendants, 
    cacheRequestRuntimeId, 
    this); 

...

public void HandleAutomationEvent(IUIAutomationElement sender, int eventId) 
{ 
    // Got a window opened event... 

    // Get the RuntimeId from the source element. Because that data is cached with the 
    // event, we don't have to call back through UIA into the provider process here. 
    int[] runtimeId = sender.GetCachedPropertyValue(propertyRuntimeId); 
} 

На стороне записки, когда это практически, я всегда данных кэша при работе с событиями или доступа к элементам через МАУ (используя вызовы, такие как FindFirstBuildCache(),), так как я хочу избежать как можно большего количества вызовов с перекрестной обработкой.

Так что мой совет будет:

  1. Используйте родной для Windows МАУ API с управляемой оболочки, порожденной Tlbimp.exe.
  2. Сбросьте как можно больше данных с событиями, чтобы избежать необходимости переходить в процесс провайдера без необходимости позже.
  3. Избегайте переадресации вызовов в МАУ из обработчика событий UIA.

Спасибо,

Guy

+0

Привет, парень, теперь я заметил, что я уже использовал очередь синхронизации, чтобы избежать вызова какого-либо кода в потоке обработчика событий, извините за то, что вы вводите вас в заблуждение. Таким образом, в обработчике событий ничего не выполняется, кроме как передать действие, которое мы хотим выполнить, в другой поток вместе с параметрами, предоставленными обработчиком событий, который точно так же, как вы рекомендовали. К сожалению, одно и то же явление все еще проявляется (некоторые события просто останавливаются в течение длительных периодов времени). Считаете ли вы, что кэширование всех необходимых свойств может оказать влияние? –

+0

Привет, я думаю, было бы полезно получить как можно больше полезной информации при доступе к элементам МАУ. Может возникнуть проблема с настройкой запросов кеша, но как только у вас есть несколько в коде, довольно быстро скопировать/вставить/отредактировать те, что вам нужно. Первоначальная стоимость межпроцессного вызова может быть довольно высокой по сравнению с другими вещами, которые выполняет МАУ, поэтому я всегда стараюсь избегать их там, где это необходимо. Боюсь, я действительно не знаю, повлияет ли добавление большего количества кеширования на то, чтобы избежать некоторых из существующих вызовов сквозной обработки, что повлияет на задержки, которые вы видите, но я думаю, что это стоит изучить. –

+0

На самом деле, я попытался кэшировать регистрацию событий в соответствии с вашим примером. К моему большому удивлению, был очень заметный удар производительности, который я действительно не могу объяснить, все события были получены с большой задержкой. Я чувствую, что мой первоначальный вопрос, возможно, был неправильно понят: никаких задержек, событий смены структуры (и открытия окна/закрытия) перестают получать через некоторое время в течение нескольких минут за раз. –