Getting a slice of idle processing in managed component under unmanaged host
.net, async-await, c#, com, winforms
Solution
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.
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.
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.
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.
Problem
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). However in this hosted scenario, `Application.Idle` doesn't get fired, because there is no managed message loop (i.e., no `Application.Run`). 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. So far, the only solution I can think of is to use plain Win32 timers. According to this (now perished) MSKB article, `WM_TIMER` has one of the lowest priorities, followed only by `WM_PAINT`, which should get me as close to the idle as possible. Am I missing any other options for this scenario? 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; } } ``` 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. Updated, I found one more option: WH_FOREGROUNDIDLE/ForegroundIdleProc. Looks exactly like what I need. Updated, I also found that a Win32 timer trick is used by WPF for low-priority Dispatcher operations, i.e. `Dispatcher.BeginInvoke(DispatcherPriority.Background, ...)`: