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
),并可用的