c# - 在非托管主机下的托管组件中获取空闲处理片段

标签 c# .net winforms com async-await

我有一个用 C# 编写的托管组件,它作为 ActiveX 控件由旧版 Win32 应用托管。在我的组件内部,我需要能够获得通常为 Application.Idle 的内容事件,即获取UI线程空闲处理时间的时间片(必须是主UI线程)。

但是在这个托管场景中,Application.Idle不会被解雇,因为没有托管消息循环(即没有 Application.Run )。

遗憾的是,主机也没有实现 IMsoComponentManager ,这可能适合我的需要。出于许多充分的理由,冗长的嵌套消息循环(使用 Application.DoEvents)不是一种选择。

到目前为止,我能想到的唯一解决方案是使用普通 Win32 timers . 据此(现已灭亡)MSKB article , WM_TIMER具有最低优先级之一,仅次于 WM_PAINT ,这应该让我尽可能接近闲置。

对于这种情况,我是否遗漏了任何其他选项?

这是一个原型(prototype)代码:

// Do the idle work in the async loop

while (true)
{
    token.ThrowIfCancellationRequested();

    // yield via a low-priority WM_TIMER message
    await TimerYield(DELAY, token); // e.g., DELAY = 50ms

    // check if there is a pending user input in Windows message queue
    if (Win32.GetQueueStatus(Win32.QS_KEY | Win32.QS_MOUSE) >> 16 != 0)
        continue;

    // do the next piece of the idle work on the UI thread
    // ...
}       

// ...
    
static async Task TimerYield(int delay, CancellationToken token) 
{
    // All input messages are processed before WM_TIMER and WM_PAINT messages.
    // System.Windows.Forms.Timer uses WM_TIMER 
    // This could be further improved to re-use the timer object

    var tcs = new TaskCompletionSource<bool>();
    using (var timer = new System.Windows.Forms.Timer())
    using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: true))
    {
        timer.Interval = delay;
        timer.Tick += (s, e) => tcs.TrySetResult(true);
        timer.Enabled = true;
        await tcs.Task;
        timer.Enabled = false;
    }
}

    

我不认为Task.Delay适合这种方法,因为它使用独立于消息循环及其优先级的内核定时器对象。

已更新,我又找到了一个选项:WH_FOREGROUNDIDLE/ForegroundIdleProc .看起来正是我需要的。

已更新,我还发现了一个 Win32 定时器技巧 is used由 WPF 用于低优先级 Dispatcher 操作,即 Dispatcher.BeginInvoke(DispatcherPriority.Background, ...) :

最佳答案

嗯,WH_FOREGROUNDIDLE/ForegroundIdleProc钩子(Hook)很棒。它的行为方式与 Application.Idle 非常相似:当线程的消息队列为空时调用钩子(Hook),并且底层消息循环的 GetMessage 调用即将进入阻塞等待状态。

但是,我忽略了一件重要的事情。事实证明,我正在处理的主机应用程序有自己的计时器,它的 UI 线程不断且相当频繁地发送 WM_TIMER 消息。如果我首先使用 Spy++ 查看它,我本可以了解到这一点。

对于 ForegroundIdleProc(对于 Application.Idle,就此而言),WM_TIMER 与任何其他消息没有区别。在分派(dispatch)每个新的 WM_TIMER 并且队列再次变空后,将调用该 Hook 。这导致 ForegroundIdleProc 比我真正需要的更频繁地被调用。

无论如何,尽管有外来计时器消息,ForegroundIdleProc 回调仍然指示线程队列中没有更多的用户输入消息(即,键盘和鼠标空闲)。因此,我可以开始我的空闲工作并使用 async/await 实现一些节流逻辑,以保持 UI 响应。这就是它与我最初基于计时器的方法的不同之处。

关于c# - 在非托管主机下的托管组件中获取空闲处理片段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20392681/

相关文章:

c# - 简单的多线程问题

c# - MVC 4 GeneratePasswordResetToken 如何生成一个新的?

c# - 维护序数集合的正确顺序

c# - 在 C# 中完成方法指南和最佳实践

c# - 如何在 VS 中链接用两种不同语言(如 C++ 和 C#)编写的两个不同项目?

c# - 如何使 RadioButton 文本和附加标签保持对齐?

c# - 如何通过辅助方法打印当前执行的方法名?

c# - 从 C++ 非托管应用程序检索 C# .NET 镜像

c# - 如何在 C# .NET 中捕获打印作业

c# - 面板——滚动条可见事件