我有一个用C#编写的托管组件,它由一个传统的Win32应用程序作为ActiveX控件托管.在我的组件内部,我需要能够获得通常为
Application.Idle
事件的内容,即获取UI线程上的空闲处理时间的时间片(它必须是主UI线程).
但是,在此托管方案中,Application.Idle不会被触发,因为没有托管消息循环(即,没有Application.Run).
遗憾的是,主持人也没有实施IMsoComponentManager
,这可能适合我的需要.由于很多好的原因,冗长的嵌套消息循环(使用Application.DoEvents)不是一个选项.
到目前为止,我能想到的唯一解决方案是使用普通的Win32 timers.
根据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); // 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.看起来完全像我需要的.
更新后,我还发现WPF的Win32计时器技巧is used用于低优先级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响应.这与我最初的基于计时器的方法有所不同.