一段时间后,UI自动化事件在监视应用程序后停止接收,然后在一段时间后重新启动

我们正在使用Microsoft的UIAutomation框架来开发一个客户端,该客户端监视特定应用程序的事件并以不同方式响应它们。 我们已经开始使用该框架的托管版本,但由于延迟问题,请转到包含在UIACOMWrapper中的本机版本。 在我们(大规模)WPF应用程序中出现更多性能问题后,我们决定将其移至单独的终端应用程序(通过UDP将事件传输到我们的WPF应用程序),这似乎解决了所有性能问题。 唯一的问题是,似乎每隔几分钟,TabSelection,StructureChanged,WindowOpened和WindowClosed的事件就会被捕获几分钟。 令人惊讶的是,当发生这种情况时,仍会收到并处理PropertyChanged事件。 我将发布我们的事件监视器的相关代码,但这可能无关紧要,因为我们在使用Microsoft自己的AccEvent实用程序时看到了类似的行为。 我不能发布受监控应用程序的代码,因为它是专有的和机密的,我可以说它是一个承载WPF窗口的WinForms应用程序,也非常庞大。 有没有人在使用UI自动化框架时看到过这种行为? 感谢您的时间。

这是监视器代码(我知道事件处理在这里的UI自动化线程上,但是将它移动到专用线程并没有改变任何东西):

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

编辑:我忘了提到我强烈怀疑问题是UI-Automation事件处理程序线程之一以某种方式卡住了。 我相信这一点的原因是,当我的显示器出现问题时,我启动了一个AccEvent实例,它收到了我的显示器没有得到的所有丢失的事件。 这意味着事件被触发但未传递给我的监视器。

编辑2:我忘了提到这种情况发生在Windows 8中运行的特定目标应用程序,我没有在我自己的Windows 7机器上看到这种现象与其他应用程序。 另一个有趣的事情是,它似乎或多或少地定期发生,但无论何时我订阅事件,即它可能在订阅后几乎立即发生,但随后需要几分钟才能再次发生。

我担心我不知道你所看到的延误的原因,但这里有一些想法……

我在下面说的所有内容都与Windows中的本机UIA API有关,而不是托管的.NET UIA API。 近年来对UIA的所有改进都是针对Windows UIA API进行的。 因此,每当我编写UIA客户端C#代码时,我都会通过使用tlbimp.exe SDK工具生成的托管包装器调用UIA。

也就是说,我首先使用像…这样的命令生成包装器

“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

然后我在我的C#项目中包含对Interop.UIAutomationCore.dll的引用,添加“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事件处理程序。 所以当时,我故意在我的事件处理程序中没有进行任何UIA调用。 相反,我会给自己发布一条消息或向队列中添加一些动作,允许我的事件处理程序返回,并在不久之后在另一个线程上采取我想要的任何动作来响应事件。 这需要我做更多的工作,但我不想冒险打击延迟。 我创建的任何线程都将在MTA中运行。

上面描述的操作示例在我的旧焦点跟踪示例中,位于https://code.msdn.microsoft.com/windowsapps/Windows-7-UI-Automation-6390614a/sourcecode?fileId=21469&pathId=715901329 。 文件FocusEventHandler.cs创建MTA线程并对消息进行排队,以避免在事件处理程序内进行UIA调用。

从Window 7开始,我知道UIA中与线程和延迟有关的限制已经放宽,并且遇到延迟的可能性已经降低。 最近,Windows 8.1和Windows 10在这个领域有一些改进,所以如果在Windows 10上运行你的代码是切实可行的,那么看看延迟是否仍然存在就很有意思。

我知道这很费时间,但您可能有兴趣在事件处理程序中删除与UIA的交互,并查看延迟是否消失。 如果他们这样做,那就是确定哪个动作似乎会引发问题,以及在没有在事件处理程序中执行UIA交互的情况下,是否有另一种实现目标的方法。

例如,在您的事件处理程序中,您调用…

this.getAutomationParent(元件).GetRuntimeId();

我希望这会导致两个回调到生成该事件的提供者应用程序。 第一个调用是获取源元素的父元素,第二个调用是获取该父元素的RuntimeId。 因此,当UIA等待你的事件处理程序返回时,你已经两次回到UIA。 虽然我不知道这是一个问题,但我会避免它。

有时,您可以通过将一些感兴趣的数据缓存到事件本身来避免跨进程调用回提供程序进程。 例如,假设我知道我想要一个引发WindowOpened事件的元素的RuntimeId。 当我注册事件时,我可以要求UIA用我收到的事件缓存该数据。

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

另外,在实际应用中,我总是在处理事件或通过UIA访问元素时缓存数据(通过使用FindFirstBuildCache()之类的调用),因为我想避免尽可能多的跨进程调用。

所以我的建议是:

  1. 将本机Windows UIA API与tlbimp.exe生成的托管包装一起使用。
  2. 使用事件尽可能多地缓存数据,以避免以后不必要地回调到提供程序进程。
  3. 避免从UIA事件处理程序内部调用UIA。

谢谢,

家伙

我在我的项目中看到过这种行为。 解决方案是取消订阅并使用计时器重新订阅事件。 另外,我在新任务(在STA线程池中运行)中引发事件之后的任何操作。