【问题标题】:Getting a slice of idle processing in managed component under unmanaged host在非托管主机下的托管组件中获取一片空闲处理
【发布时间】:2013-12-21 23:04:41
【问题描述】:

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

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

遗憾的是,主机也没有实现IMsoComponentManager,这可能适合我的需要。由于许多充分的理由,冗长的嵌套消息循环(使用Application.DoEvents)不是一种选择。

到目前为止,我能想到的唯一解决方案是使用普通的Win32 timers。 据此(现已灭亡)MSKB articleWM_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.BeginInvoke(DispatcherPriority.Background, ...)

【问题讨论】:

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


    【解决方案1】:

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

    但是,我忽略了一件重要的事情。事实上,我正在处理的主机应用程序有自己的计时器,并且它的 UI 线程不断且非常频繁地发送 WM_TIMER 消息。如果我首先使用 Spy++ 查看它,我本可以了解到这一点。

    对于ForegroundIdleProc(以及对于Application.Idle,就此而言),WM_TIMER 与任何其他消息没有什么不同。在调度每个新的WM_TIMER 并且队列再次变空后,将调用该钩子。这导致ForegroundIdleProc 被调用的频率比我真正需要的要多得多。

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

    【讨论】:

      猜你喜欢
      • 2016-02-21
      • 2014-07-09
      • 2013-11-01
      • 2011-04-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-14
      • 1970-01-01
      相关资源
      最近更新 更多