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

时间:2013-12-05 06:04:59

标签: c# .net winforms com async-await

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

但是在此托管方案中,Application.Idle不会被触发,因为没有托管消息循环(即,没有Application.Run)。

可悲的是,主持人也没有实施IMsoComponentManager,这可能适合我的需要。而且由于很多充分的原因,冗长的嵌套消息循环(Application.DoEvents)不是一个选项。

到目前为止,我能想到的唯一解决方案是使用普通Win32 timers。 根据{{​​3}},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不适合这种方法,因为它使用内核计时器对象,它独立于消息循环及其优先级。

已更新,我找到了另一个选项:http://support.microsoft.com/kb/96006。看起来完全像我需要的那样。

更新了,我还发现WPF的Win32计时器技巧WH_FOREGROUNDIDLE/ForegroundIdleProc用于低优先级Dispatcher操作,即Dispatcher.BeginInvoke(DispatcherPriority.Background, ...)

1 个答案:

答案 0 :(得分:2)

好吧,WH_FOREGROUNDIDLE/ForegroundIdleProc挂钩很棒。它的行为方式与Application.Idle非常相似:当线程的消息队列为空时,钩子被调用,而基础消息循环的GetMessage调用即将进入阻塞等待状态。

然而,我忽略了一件重要的事情。随着它的转变,我正在处理的主机应用程序有自己的计时器,它的UI线程不断地频繁地发送WM_TIMER消息。我本可以了解到,如果我用Spy ++来看它,首先。

对于ForegroundIdleProc(对于Application.Idle而言,WM_TIMER与其他任何消息都没有区别。在调度每个新WM_TIMER并且队列再次变空之后,将调用该挂钩。这导致ForegroundIdleProc的调用次数比我真正需要的频率高得多。

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