获取空闲处理的切片托管组件在非托管主机切片、组件、主机

2023-09-03 17:04:39 作者:凉薄时光葬空城

我在C#编写的托管组件,它由一个传统的Win32应用程序作为ActiveX控件承载。在我的部分,我需要能够得到什么,通常会是 Application.Idle 事件,即获得的闲置处理时间的UI线程上一个时间片(它是主UI线程)。

I have a managed component written in C#, which is hosted by a legacy Win32 app as an ActiveX control. Inside my component, I need to be able to get what normally would be Application.Idle event, i.e. obtain a time slice of the idle processing time on the UI thread (it has to be the main UI thread).

然而,在这种情况下举办, Application.Idle 不被解雇,因为没有管理消息循环(即无应用程序。运行)。

However in this hosted scenario, Application.Idle doesn't get fired, because there is no managed message loop (i.e., no Application.Run).

可悲的是,主机还没有实现的 IMsoComponentManager ,这可能是适合我的需要。而冗长的嵌套消息循环(与 Application.DoEvents )不是很多好的理由的选择。

Sadly, the host also doesn't implement IMsoComponentManager, which might be suitable for what I need. And a lengthy nested message loop (with Application.DoEvents) is not an option for many good reasons.

到目前为止,唯一的解决办法我能想到的就是用普通的 Win32的定时器。 据 http://support.microsoft.com/kb/96006 , WM_TIMER 具有最低的优先方向之一,其次才由 WM_PAINT ,这应该让我尽量接近空闲的时候。

So far, the only solution I can think of is to use plain Win32 timers. According to http://support.microsoft.com/kb/96006, WM_TIMER has one of the lowest priorities, followed only by WM_PAINT, which should get me as close to the idle as possible.

我失去了任何其他选项对于这种情况?

下面是一个原型code:

Here is a prototype code:

// 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 将适用于这种方法,因为它使用内核定时器对象,它们是独立的消息循环和重点。

I don't think Task.Delay would be suitable for this approach, as it uses Kernel timer objects, which are independent of the message loop and its priorities.

更新,我发现多了一个选项:的 WH_FOREGROUNDIDLE / ForegroundIdleProc 。长相酷似我需要什么。

Updated, I found one more option: WH_FOREGROUNDIDLE/ForegroundIdleProc. Looks exactly like what I need.

更新,我还发现了一个Win32计时器把戏用于通过WPF为低优先级调度员操作,nofollow的,即 Dispatcher.BeginInvoke(DispatcherPriority.Background,...)

Updated, I also found that a Win32 timer trick is used by WPF for low-priority Dispatcher operations, i.e. Dispatcher.BeginInvoke(DispatcherPriority.Background, ...):

推荐答案

那么,的 WH_FOREGROUNDIDLE / ForegroundIdleProc 挂钩是伟大的。它的行为在一个非常类似的方式来 Application.Idle :钩子被调用时,线程的消息队列为空,而底层的消息循环的的GetMessage 通话即将进入阻塞等待状态。

Well, WH_FOREGROUNDIDLE/ForegroundIdleProc hook is great. It behaves in a very similar way to Application.Idle: the hook gets called when the thread's message queue is empty, and the underlying message loop's GetMessage call is about to enter the blocking wait state.

不过,我忽略了一件重要的事情。事实证明,主机应用程序我处理有自己的计时器,它的UI线程不断地相当频繁抽 WM_TIMER 的消息。我可以知道,如果我看着它与间谍++,摆在首位。

However, I've overlooked one important thing. As it turns, the host app I'm dealing with has its own timers, and its UI thread is pumping WM_TIMER messages constantly and quite frequently. I could have learnt that if I looked at it with Spy++, in the first place.

有关 ForegroundIdleProc (和 Application.Idle ,对于这个问题), WM_TIMER 没有任何其他消息不同。钩后,每一个新的被称为 WM_TIMER 已派出和队列已经变空了。这导致了 ForegroundIdleProc 被称为更往往比我真正需要的。

For ForegroundIdleProc (and for Application.Idle, for that matter), WM_TIMER is no different from any other message. The hook gets called after each new WM_TIMER has been dispatched and the queue has become empty again. That results in ForegroundIdleProc being called much more often than I really need.

总之,尽管外星人定时器消息,在 ForegroundIdleProc 回调仍表示有在线程的队列中没有更多的用户输入的信息(即键盘和鼠标处于空闲状态)。因此,我可以在它开始我的无功和使用实行一些限制逻辑异步 / 等待,以保持用户界面反应灵敏。这是怎么会从我最初的基于定时器的方式不同。

Anyway, despite the alien timer messages, the ForegroundIdleProc callback still indicates there is no more user input messages in the thread's queue (i.e., keyboard and mouse are idle). Thus, I can start my idle work upon it and implement some throttling logic using async/await, to kept the UI responsive. This is how it would be different from my initial timer-based approach.