我有一个用 C# 编写的托管组件,它由旧版 Win32 应用程序作为 ActiveX 控件托管。在我的组件内部,我需要能够获得通常情况下的内容Application.Idle http://msdn.microsoft.com/en-us/library/system.windows.forms.application.idle%28v=vs.110%29.aspx事件,即获取 UI 线程上的空闲处理时间的时间片(必须是主 UI 线程)。
然而在这个托管场景中,Application.Idle
不会被解雇,因为没有托管消息循环(即,没有Application.Run
).
可悲的是,主机也没有实现IMsoComponentManager http://msdn.microsoft.com/en-us/library/office/ff518963(v=office.12).aspx,这可能适合我的需要。以及一个冗长的嵌套消息循环(带有Application.DoEvents
) 出于许多充分的理由而不是一个选择。
到目前为止,我能想到的唯一解决方案是使用普通Win32 定时器 http://msdn.microsoft.com/en-us/library/windows/desktop/ms632592(v=vs.85).aspx。
据此(现已灭亡)MSKB 文章 https://web.archive.org/web/20130627005845/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
适合这种方法,因为它使用内核计时器对象,这些对象独立于消息循环及其优先级。
Updated,我又发现了一个选择:WH_FOREGROUNDIDLE/ForegroundIdleProc http://msdn.microsoft.com/en-us/library/windows/desktop/ms644980%28v=vs.85%29.aspx。看起来和我需要的一模一样。
Updated,我还发现了一个Win32计时器技巧is used http://referencesource.microsoft.com/#WindowsBase/src/Base/System/Windows/Threading/Dispatcher.cs#ad208569500b2a1d由 WPF 用于低优先级调度程序操作,即Dispatcher.BeginInvoke(DispatcherPriority.Background, ...)
: