我有一个用 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/