首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >UI自动化事件在监视应用程序一段时间后停止接收,然后在一段时间后重新启动

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

Stack Overflow用户
提问于 2015-09-02 16:04:10
回答 2查看 4.4K关注 0票数 12

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

以下是监视器代码(我知道事件处理在这里的UI Automation线程上,但将其移动到专用线程并没有改变任何事情):

代码语言:javascript
复制
        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机器上和其他应用程序一起看到这种现象。另一件有趣的事情是,它似乎或多或少是周期性的,但无论我什么时候订阅事件,也就是说,它几乎可以在订阅后立即发生,但随后需要几分钟才能再次发生。

EN

回答 2

Stack Overflow用户

发布于 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#文件中,然后我可以执行以下操作:

代码语言:javascript
复制
IUIAutomation uiAutomation = new CUIAutomation8();

IUIAutomationElement rootElement = uiAutomation.GetRootElement();

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

..。

代码语言:javascript
复制
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将这些数据与我收到的事件一起缓存。

代码语言:javascript
复制
int propertyRuntimeId = 30000; // UIA_RuntimeIdPropertyId

..。

代码语言:javascript
复制
IUIAutomationCacheRequest cacheRequestRuntimeId = uiAutomation.CreateCacheRequest();
cacheRequestRuntimeId.AddProperty(propertyRuntimeId);

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

..。

代码语言:javascript
复制
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和tlbimp.exe.
  2. Cache生成的托管包装尽可能多地处理事件,以避免以后不必要地回调到提供程序进程。
  3. 避免从UIA事件处理程序内部回调到。

谢谢,

盖伊

票数 5
EN

Stack Overflow用户

发布于 2015-09-11 12:37:04

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

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32347734

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档