栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

StaTaskScheduler和STA线程消息泵送

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

StaTaskScheduler和STA线程消息泵送

我对您的问题的理解:您

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
),可在此处作为独立的控制台应用程序使用。它尚未经过全面测试,因此使用时需您自担风险。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/370231.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号