我们正在使用微软的UIAutomation框架来开发一个客户端,它可以监控特定应用程序的事件,并以不同的方式响应它们。我们从框架的托管版本开始,但由于延迟问题,移动到了包装在UIACOMWrapper中的本机版本。在我们的(大规模) WPF应用程序内部出现了更多的性能问题后,我们决定将其移动到一个单独的终端应用程序(通过UDP将事件传输到我们的WPF应用程序),这似乎可以解决所有的性能问题。唯一的问题是,似乎每隔几分钟,TabSelection、StructureChanged、WindowOpened和WindowClosed的事件就会停止捕获几分钟。令人惊讶的是,在发生这种情况时,仍然会接收和处理PropertyChanged事件。我将发布我们的事件监视器的相关代码,但这可能是无关紧要的,因为我们在使用微软自己的AccEvent实用程序时看到了类似的行为。我不能发布被监控的应用程序的代码,因为它也是专有的和机密的,我可以说它是一个托管WPF窗口的WinForms应用程序,也是相当庞大的。在使用UI自动化框架时,有没有人看到过这种行为?谢谢您抽时间见我。
以下是监视器代码(我知道事件处理在这里的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());
}
}编辑:我忘了提到,我强烈怀疑这个问题是UI-Automation事件处理程序线程之一以某种方式被卡住了。我之所以相信这一点,是因为当问题发生在我的监视器中时,我启动了一个AccEvent实例,它收到了监视器没有得到的所有丢失的事件。这意味着事件正在被触发,但不会传递给我的监视器。
EDIT2:我忘记提到在Windows8和特定的目标应用程序一起运行时会发生这种情况,我还没有在我自己的Windows7机器上和其他应用程序一起看到这种现象。另一件有趣的事情是,它似乎或多或少是周期性的,但无论我什么时候订阅事件,也就是说,它几乎可以在订阅后立即发生,但随后需要几分钟才能再次发生。
发布于 2015-09-26 00:03:08
恐怕我不知道你所看到的延迟的原因,但这里有一些想法…
下面我所说的一切都与Windows中的原生UIA有关,而不是托管的.NET UIA。近年来对UIA的所有改进都是针对Windows UIA API进行的。因此,每当我编写UIA客户端C#代码时,我都会通过使用tlbimp.exe SDK工具生成的托管包装器来调用UIA。
也就是说,我首先使用如下命令生成包装器...
"C:\Program Files (x86)\Microsoft \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...
}在Windows7中,围绕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中与线程和延迟相关的限制已经放宽,遇到延迟的可能性也降低了。最近,Windows8.1和Windows10在这方面有了一些改进,所以如果在Windows10上运行您的代码是可行的,那么看看延迟是否仍然存在将是一件有趣的事情。
我知道这很耗时,但您可能有兴趣删除事件处理程序中与UIA的交互,并查看延迟是否消失。如果是这样,就需要确定哪个操作似乎触发了问题,并查看是否有其他方法可以在不执行事件处理程序中的UIA交互的情况下实现您的目标。
例如,在您的事件处理程序中,您调用...
this.getAutomationParent(element).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()等调用),因为我希望避免尽可能多的跨进程调用。
所以我的建议是:
谢谢,
盖伊
发布于 2015-09-11 12:37:04
我在我的项目中看到过这种行为。解决方案是取消订阅,然后使用计时器重新订阅事件。此外,我在一个新任务(在STA线程池中运行)中的事件之后启动了任何操作。
https://stackoverflow.com/questions/32347734
复制相似问题