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 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.
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, ...)
: