我对您的问题的理解:您
StaTaskScheduler仅用于为旧版COM对象组织经典的COM
STA公寓。您 不在
的STA线程上运行WinForms或WPF核心消息循环
StaTaskScheduler。也就是说,你没有使用像什么
Application.Run,
Application.DoEvents或者
Dispatcher.Pushframe该线程内。如果这是一个错误的假设,请纠正我。
就其本身而言,
StaTaskScheduler不会 在其创建的STA线程上 安装
任何同步上下文。因此,您依赖CLR为您发送消息。我仅在克里斯·布鲁姆(Chris
Brumme)的《
CLR中的公寓和泵送》中发现了一个隐式确认,即CLR在STA线程上泵送:
我一直说,在STA线程上调用时,托管阻塞将执行“一些泵送”。确切知道将要泵出什么不是很好吗?不幸的是,抽水是一种妖术,无法超越凡人的理解。在Win2000及更高版本上,我们仅委托给OLE32的
CoWaitForMultipleHandles 服务。
这表明CLR在
CoWaitForMultipleHandles内部将其用于STA线程。此外,用于
COWAIT_DISPATCH_WINDOW_MESSAGES标记的MSDN文档提到了这一点:
……在STA中,只有一小部分特殊情况的消息被分发。
我对此进行了一些研究,但无法
WM_TEST从的示例代码中抽取
CoWaitForMultipleHandles,我们在对您的问题的评论中进行了讨论。我的理解是,前面提到的一
小部分特殊情况的消息 实际上仅限 于某些COM编组器特定的消息,并且不包括任何常规的通用消息,例如your
WM_TEST。
因此,回答您的问题:
…我是否应该实现一个自定义同步上下文,该上下文将显式地使用CoWaitForMultipleHandles泵送消息,并将其安装在由StaTaskScheduler启动的每个STA线程上?
是的,我相信创建自定义同步上下文和覆盖
SynchronizationContext.Wait确实是正确的解决方案。
但是,您应该避免使用
CoWaitForMultipleHandles,而 改为
使用
MsgWaitForMultipleObjectsEx。如果
MsgWaitForMultipleObjectsEx指示队列中有待处理的消息,则应使用
PeekMessage(PM_REMOVE)和手动泵送它
DispatchMessage。然后,您应该继续等待所有相同
SynchronizationContext.Wait调用中的句柄。
请注意,和之间 存在细微但重要的区别
。如果队列中已经看到一条消息(例如,带有或),但没有删除,则后者不会返回并保持阻塞状态。这对泵送不利,因为您的COM对象可能正在使用诸如检查消息队列之类的方法。以后可能会导致阻塞,这是不可预期的。
MsgWaitForMultipleObjectsEx
MsgWaitForMultipleObjects
PeekMessage(PM_NOREMOVE)``GetQueueStatus``PeekMessage``MsgWaitForMultipleObjects
MsgWaitForMultipleObjectsEx带有
MWMO_INPUTAVAILABLE标志的OTOH 没有这种缺点,在这种情况下会返回。
不久前,我创建了一个自定义版本的
StaTaskScheduler(可在此处获得
ThreadAffinityTaskScheduler),以尝试解决另一个问题:维护一个具有线程亲和力的线程池以用于后续
await继续。如果跨多个使用STA COM对象,则线程亲和性
至关重要
awaits。
StaTaskScheduler仅当其原始存储池限制为1个线程时,才会显示此行为。
因此,我继续尝试了更多有关您的
WM_TEST案例的实验。最初,我
SynchronizationContext在STA线程上安装了标准类的实例。该
WM_TEST消息没有得到抽,这是预期。
然后我重写
SynchronizationContext.Wait以将其转发给
SynchronizationContext.WaitHelper。它确实被调用了,但是仍然没有启动。
最后,我实现了功能全面的消息泵循环,这是它的核心部分:
// the core loopvar msg = new NativeMethods.MSG();while (true){ // MsgWaitForMultipleObjectsEx with MWMO_INPUTAVAILABLE returns, // even if there's a message already seen but not removed in the message queue nativeResult = NativeMethods.MsgWaitForMultipleObjectsEx( count, waitHandles, (uint)remainingTimeout, QS_MASK, NativeMethods.MWMO_INPUTAVAILABLE); if (IsNativeWaitSuccessful(count, nativeResult, out managedResult) || WaitHandle.WaitTimeout == managedResult) return managedResult; // there is a message, pump and dispatch it if (NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, NativeMethods.PM_REMOVE)) { NativeMethods.TranslateMessage(ref msg); NativeMethods.DispatchMessage(ref msg); } if (hasTimedOut()) return WaitHandle.WaitTimeout;}这确实起作用,WM_TEST
被抽出。以下是测试的改编版本:
public static async Task RunAsync(){ using (var staThread = new Noseratio.ThreadAffinity.ThreadWithAffinityContext(staThread: true, pumpMessages: true)) { Console.WriteLine("Initial thread #" + Thread.CurrentThread.ManagedThreadId); await staThread.Run(async () => { Console.WriteLine("On STA thread #" + Thread.CurrentThread.ManagedThreadId); // create a simple Win32 window IntPtr hwnd = CreateTestWindow(); // Post some WM_TEST messages Console.WriteLine("Post some WM_TEST messages..."); NativeMethods.PostMessage(hwnd, NativeMethods.WM_TEST, new IntPtr(1), IntPtr.Zero); NativeMethods.PostMessage(hwnd, NativeMethods.WM_TEST, new IntPtr(2), IntPtr.Zero); NativeMethods.PostMessage(hwnd, NativeMethods.WM_TEST, new IntPtr(3), IntPtr.Zero); Console.WriteLine("Press Enter to continue..."); await ReadLineAsync(); Console.WriteLine("After await, thread #" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("Pending messages in the queue: " + (NativeMethods.GetQueueStatus(0x1FF) >> 16 != 0)); Console.WriteLine("Exiting STA thread #" + Thread.CurrentThread.ManagedThreadId); }, CancellationToken.None); } Console.WriteLine("Current thread #" + Thread.CurrentThread.ManagedThreadId);}输出 :
初始线程#9在STA线程#10上发布一些WM_TEST消息...按Enter继续...WM_TEST已处理:1WM_TEST已处理:2WM_TEST已处理:3等待之后,线程#10队列中的待处理消息:False退出STA线程#10当前线程#12按任何一个键退出
请注意,此实现同时支持线程亲缘关系(它位于之后的#10线程上
await)和消息泵送。完整的源代码包含可重复使用的部分(
ThreadAffinityTaskScheduler和
ThreadWithAffinityContext),可在此处作为独立的控制台应用程序使用。它尚未经过全面测试,因此使用时需您自担风险。



