在非托管主机下获取托管组件中的空闲处理片段
我有一个用C#编写的托管组件,它由一个传统的Win32应用程序作为ActiveX控件托管。 在我的组件中,我需要能够获得通常是Application.Idle
事件,即获取UI线程上的空闲处理时间的时间片(它必须是主UI线程)。
但是,在此托管方案中, Application.Idle
不会被触发,因为没有托管消息循环(即,没有Application.Run
)。
遗憾的是,主机也没有实现IMsoComponentManager
,它可能适合我需要的东西。 由于许多好的原因,冗长的嵌套消息循环(使用Application.DoEvents
)不是一个选项。
到目前为止,我能想到的唯一解决方案是使用普通的Win32定时器 。 根据http://support.microsoft.com/kb/96006,WM_TIMER具有最低优先级之一,仅遵循WM_PAINT
,这应该让我尽可能接近空闲。
我是否缺少此方案的其他选项?
这是一个原型代码:
// Do the idle work in the async loop while (true) { token.ThrowIfCancellationRequested(); // yield via a low-priority WM_TIMER message await TimerYield(DELAY, token); // eg, 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(); 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 。 看起来完全像我需要的。
更新后 ,我还发现WPF 使用 Win32计时器技巧进行低优先级Dispatcher操作,即Dispatcher.BeginInvoke(DispatcherPriority.Background, ...)
:
好吧, WH_FOREGROUNDIDLE / ForegroundIdleProc挂钩很棒。 它的行为方式与Application.Idle
非常相似:当线程的消息队列为空时,挂钩被调用,而底层消息循环的GetMessage
调用即将进入阻塞等待状态。
但是,我忽略了一件重要的事情。 随着它的转变,我正在处理的主机应用程序有自己的计时器,它的UI线程不断地频繁地抽取WM_TIMER
消息。 我本可以了解到,如果我用Spy ++来看它,首先。
对于ForegroundIdleProc
(对于Application.Idle
,就此而言), WM_TIMER
与任何其他消息没有区别。 在调度每个新的WM_TIMER
并且队列再次变空之后调用该钩子。 这导致ForegroundIdleProc
的调用次数比我真正需要的频率高得多。
无论如何,尽管有外来计时器消息, ForegroundIdleProc
回调仍然表明线程队列中没有更多用户输入消息(即键盘和鼠标空闲)。 因此,我可以开始我的空闲工作,并使用async
/ await
实现一些限制逻辑,以保持UI响应。 这与我最初的基于计时器的方法有所不同。