StaTaskScheduler和STA线程消息抽线程、消息、StaTaskScheduler、STA

2023-09-02 20:41:30 作者:尘世中的一天

TL; DR:按 StaTaskScheduler 运行任务内的死锁的长版:

我用StaTaskScheduler从 ParallelExtensionsExtras 通过平行小组,举办由第三方提供的一些遗留STA COM对象。的 StaTaskScheduler 实施细节的描述说以下内容:

  

好消息是,太平人寿实现能够要么运行   MTA或STA线程,并考虑到相关的差异各地   底层的API,如WaitHandle.WaitAll的(仅支持MTA   当该方法提供多个等待句柄)的螺纹。

我以为这将意味着太平人寿的阻断部分将使用一个等待API,它泵的消息,像CoWaitForMultipleHandles,避免死锁情况在一个STA线程调用时。

在我的情况,我相信下面的发生:在进程内STA COM对象A进行调用以外的进程内对象B,然后期望从B中的回调,通过作为呼出的一部分

在简化形式:

  VAR的结果=等待Task.Factory.StartNew(()=>
{
    //在进程内对象A
    VAR一个=新的A();
    //外的进程内对象B
    变种B =新的B();
    // A调用B和B的方法调用过程中调用时A
    返回a.Method(B);
},CancellationToken.None,TaskCreationOptions.None,staTaskScheduler);
 
线程池的理解

现在的问题是, a.Method(B)永远不会返回。据我所知,这是因为阻塞等待某处 BlockingCollection<任务> 不抽取消息,所以我对所援引言论的假设可能是错误的。

EDITED 当测试WinForms应用程序的UI线程上执行(即,提供相同的code工作 TaskScheduler.FromCurrentSynchronizationContext()到 Task.Factory.StartNew )。

什么是正确的方式来解决这个问题?我实现了自定义同步环境,这将明确与 CoWaitForMultipleHandles 泵的消息,并在每个STA线程通过 StaTaskScheduler ?

如果是这样,将底层实现 BlockingCollection 来叫我的SynchronizationContext.Wait方法?我可以使用SynchronizationContext.WaitHelper实施 SynchronizationContext.Wait ? EDITED 与一些code显示出做一个阻塞等待时,有管理的STA线程不抽。在code是一个完整的控制台应用程序准备复制/粘贴/运行:

 使用系统;
使用System.Collections.Concurrent;
使用了System.Runtime.InteropServices;
使用的System.Threading;
使用System.Threading.Tasks;

命名空间ConsoleTestApp
{
    类节目
    {
        //启动和运行一个STA线程
        静态无效RunStaThread(布尔泵)
        {
            //测试阻塞等待与BlockingCollection.Take
            VAR任务=新BlockingCollection<任务>();

            VAR线程=新主题(()=>
            {
                //创建一个简单的Win32窗口
                VAR hwndStatic = NativeMethods.CreateWindowEx(0,静,的String.Empty,NativeMethods.WS_POPUP,
                    0,0,0,0,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero);

                //子类,它有一个自定义的WndProc
                IntPtr的prevWndProc = IntPtr.Zero;

                VAR newWndProc =新NativeMethods.WndProc((HWND,味精,的wParam,lParam的)=>
                {
                    如果(MSG == NativeMethods.WM_TEST)
                        Console.WriteLine(WM_TEST处理);
                    返回NativeMethods.CallWindowProc(prevWndProc,HWND,味精,的wParam,lParam的);
                });

                prevWndProc = NativeMethods.SetWindowLong(hwndStatic,NativeMethods.GWL_WNDPROC,newWndProc);
                如果(prevWndProc == IntPtr.Zero)
                    抛出新ApplicationException的();

                //发布测试WM_TEST消息吧
                NativeMethods.PostMessage(hwndStatic,NativeMethods.WM_TEST,IntPtr.Zero,IntPtr.Zero);

                // BlockingCollection块不抽,NativeMethods.WM_TEST从未到达
                尝试{VAR任务= tasks.Take(); }
                赶上(例外五){Console.WriteLine(e.Message); }

                如果(泵)
                {
                    // NativeMethods.WM_TEST将抵达,因为Win32的消息框泵
                    Console.WriteLine(现在开始抽......);
                    NativeMethods.MessageBox(IntPtr.Zero,抽水消息,preSS确定停止......的String.Empty,0);
                }
            });

            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();

            Thread.sleep代码(2000);

            //这将导致STA线程结束
            tasks.CompleteAdding();

            的Thread.join();
        }

        静态无效的主要(字串[] args)
        {
            Console.WriteLine(测试不抽?);
            RunStaThread(假);

            Console.WriteLine( NTEST与抽......);
            RunStaThread(真正的);

            Console.WriteLine(preSS Enter退出);
            到Console.ReadLine();
        }
    }

    //互操作
    静态类NativeMethods
    {
        [的DllImport(USER32)]
        公共静态外部的IntPtr的SetWindowLong(IntPtr的HWND,国际nIndex,WndProc中的NewProc);

        [的DllImport(USER32)]
        公共静态外部的IntPtr CallWindowProc的(IntPtr的LP prevWndFunc,IntPtr的HWND,INT味精,诠释的wParam,lParam的INT);

        [的DllImport(user32.dll中)
        公共静态外部的IntPtr CreateWindowEx函数(INT dwExStyle参数,字符串lpClassName,串lpWindowName,诠释dwStyle,INT X,INT Y,INT nWidth,nHeight参数INT,IntPtr的hWndParent,IntPtr的HMENU,IntPtr的实例句柄,IntPtr的lpParam);

        [的DllImport(user32.dll中)
        公共静态的extern BOOL PostMessage的(IntPtr的HWND,UINT味精,IntPtr的wParam中,IntPtr的lParam的);

        [的DllImport(user32.dll中)
        公共静态外部INT的MessageBox(IntPtr的HWND,文本字符串,字符串标题,诠释选项);

        公共委托的IntPtr的WndProc(IntPtr的HWND,诠释味精,诠释的wParam,lParam的INT);

        公共const int的GWL_WNDPROC = -4;
        公共const int的WS_POPUP =选中((INT)为0x80000000);
        公共const int的WM_USER = 0x0400的时候;

        公共const int的WM_TEST = WM_USER + 1;
    }
}
 

这产生的输出:

测试不抽...
该集合的说法是空的,已被标记为完成关于补充。

测试用抽...
该集合的说法是空的,已被标记为完成关于补充。
现在开始抽...
WM_TEST处理
preSS Enter退出

解决方案

我对你的问题的理解:您正在使用StaTaskScheduler只有组织传统的COM STA公寓为您传统的COM对象。你的不可以 StaTaskScheduler 的STA线程上运行一个WinForms或WPF核心消息循环。也就是说,你不使用任何类似 Application.Run Application.DoEvents Dispatcher.PushFrame 的线程中。纠正我,如果这是一个错误的假设。

就其本身而言, StaTaskScheduler 不安装在其创建的STA线程的任何同步环境。因此,你依靠的CLR泵邮件给你。我只找到了一个隐含的确认CLR的泵上的STA线程,在公寓和泵送在CLR 通过克里斯Brumme:

  

我一直在说,管堵将执行一些抽水的时候   叫上一个STA线程。那岂不是巨大的确切地知道   会变得僵硬?不幸的是,抽水是一个黑色的艺术,是   凡人COM prehension。在Win2000及以上者,我们只是委托给   OLE32的 CoWaitForMultipleHandles 服务。

这表示CLR使用CoWaitForMultipleHandles内部为STA线程。此外,MSDN文档的 COWAIT_DISPATCH_WINDOW_MESSAGES 标记的提到这一点:

  

...在STA为只有一小套分派特例消息

我做some上研究,但未能得到泵 WM_TEST 从您的样品code与 CoWaitForMultipleHandles ,我们讨论了在评论你的问题。我的理解是,上述的小部分特例消息的实在有限以某些COM封送处理特定的消息,并且不包括任何常规的通用信息样你的 WM_TEST

因此​​,要回答你的问题:

  

......应我实现了一个自定义同步的背景下,这将   明确抽取消息与CoWaitForMultipleHandles,并安装它   每个STA线程通过StaTaskScheduler开始?

是的,我认为,创建一个自定义同步上下文并覆盖 SynchronizationContext.Wait 确实是正确的解决方案。

不过,你应该避免使用 CoWaitForMultipleHandles 和使用MsgWaitForMultipleObjectsEx而不是。如果 MsgWaitForMultipleObjectsEx 表示有在队列中挂起消息时,应手动的PeekMessage(PM_REMOVE)泵它在DispatchMessage 。然后,你应该继续等待把手,里面所有相同的 SynchronizationContext.Wait 电话。

请注意有一个微妙但重要的区别的 MsgWaitForMultipleObjectsEx 和MsgWaitForMultipleObjects.后者不返回并保持阻塞,如果有已经看到在队列中(例如,用的PeekMessage(PM_NOREMOVE) GetQueueStatus ),但不会被删除。这不是好抽,因为你的COM对象可能会使用类似的PeekMessage 来检查消息队列。这可能导致以后 MsgWaitForMultipleObjects 时,预计不会阻拦。

OTOH, MsgWaitForMultipleObjectsEx MWMO_INPUTAVAILABLE 标志不会有这样的缺点,并且将返回在这种情况下。

前段时间我在 StaTaskScheduler 的定制版本(的可在这里为 ThreadAffinityTaskScheduler )在试图解决一个different问题:保持与线程关联的线程进行后续 A泳池旁延续。线程亲和力的重要如果您使用多个STA COM对象等待。原来的StaTaskScheduler展品这种行为仅当它的池被限制为1个线程。

所以,我继续做了一些更多的与你的 WM_TEST 情况下尝试。本来,我安装的是标准的一个实例SynchronizationContext类STA线程上。该 WM_TEST 消息没有得到抽,这是预期。

然后我重写SynchronizationContext.Wait只是转发给SynchronizationContext.WaitHelper.它没有被调用,但仍然没有抽。

最后,我实现了一个功能齐全的消息泵循环,这里是它的核心部分:

  //核心圈
VAR味精=新NativeMethods.MSG();
而(真)
{
    // MsgWaitForMultipleObjectsEx与MWMO_INPUTAVAILABLE回报,
    //即使有已经看到了消息,但在消息队列不会被删除
    nativeResult = NativeMethods.MsgWaitForMultipleObjectsEx(
        算,waitHandles,
        (UINT)remainingTimeout,
        QS_MASK,
        NativeMethods.MWMO_INPUTAVAILABLE);

    如果(IsNativeWaitSuccessful(计数,nativeResult,出managedResult)|| WaitHandle.WaitTimeout == managedResult)
        返回managedResult;

    //有消息,泵和派遣它
    如果(NativeMethods.PeekMessage(出味精,IntPtr.Zero,0,0,NativeMethods.PM_REMOVE))
    {
        NativeMethods.TranslateMessage(REF MSG);
        NativeMethods.DispatchMessage(REF MSG);
    }
    如果(hasTimedOut())
        返回WaitHandle.WaitTimeout;
}
 

这不工作, WM_TEST 被抽下面是测试的改编版:

 公共静态异步任务RunAsync()
{
    使用(VAR staThread =新Noseratio.ThreadAffinity.ThreadWithAffinityContext(staThread:真,pumpMessages:真))
    {
        Console.WriteLine(初始线程#+ Thread.CurrentThread.ManagedThreadId);
        等待staThread.Run(异步()=>
        {
            Console.WriteLine(在STA线程#+ Thread.CurrentThread.ManagedThreadId);
            //创建一个简单的Win32窗口
            IntPtr的HWND = CreateTestWindow();

            //张贴一些WM_TEST消息
            Console.WriteLine(后一些WM_TEST的消息......);
            NativeMethods.PostMessage(HWND,NativeMethods.WM_TEST,新的IntPtr(1),IntPtr.Zero);
            NativeMethods.PostMessage(HWND,NativeMethods.WM_TEST,新的IntPtr(2),IntPtr.Zero);
            NativeMethods.PostMessage(HWND,NativeMethods.WM_TEST,新的IntPtr(3),IntPtr.Zero);
            Console.WriteLine(preSS回车键继续......);
            等待ReadLineAsync();

            Console.WriteLine(后的await,线程#+ Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine(等待队列中的消息:+(NativeMethods.GetQueueStatus(到0x1FF)>> 16 = 0)!);

            Console.WriteLine(退出STA线程#+ Thread.CurrentThread.ManagedThreadId);
        },CancellationToken.None);
    }
    Console.WriteLine(当前线程#+ Thread.CurrentThread.ManagedThreadId);
}
 

输出

初始线程#9
在STA线程#10
张贴一些WM_TEST信息...
preSS回车键继续......
WM_TEST处理:1
WM_TEST处理:2
WM_TEST处理:3

计谋后,线#10
待队列中的消息:假
退出STA线程#10
当前线程#12
preSS任意键退出

请注意此实现支持的线程关联性(后停留在线#10等候)和消息抽水。完整的源$ C ​​$ C中含有的可重复使用的部分( ThreadAffinityTaskScheduler ThreadWithAffinityContext ),并可用的

 
精彩推荐
图片推荐