Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/267.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# StaTaskScheduler和STA线程消息泵_C#_.net_Com_Task Parallel Library_Async Await - Fatal编程技术网

C# StaTaskScheduler和STA线程消息泵

C# StaTaskScheduler和STA线程消息泵,c#,.net,com,task-parallel-library,async-await,C#,.net,Com,Task Parallel Library,Async Await,TL;DR:StaTaskScheduler运行的任务中的死锁。长版本: 我正在使用并行团队提供的from来托管一些由第三方提供的遗留STA COM对象。StaTaskScheduler实现详细信息的描述如下: 好消息是TPL的实现可以在任何一个平台上运行 MTA或STA线程,并考虑周围的相关差异 WaitHandle.WaitAll(仅支持MTA)等底层API 线程(当该方法提供多个等待句柄时) 我认为这意味着TPL的阻塞部分将使用一个wait API来传输消息,比如在STA线程上调用时避免死

TL;DR:StaTaskScheduler运行的任务中的死锁。长版本:

我正在使用并行团队提供的from来托管一些由第三方提供的遗留STA COM对象。
StaTaskScheduler
实现详细信息的描述如下:

好消息是TPL的实现可以在任何一个平台上运行 MTA或STA线程,并考虑周围的相关差异 WaitHandle.WaitAll(仅支持MTA)等底层API 线程(当该方法提供多个等待句柄时)

我认为这意味着TPL的阻塞部分将使用一个wait API来传输消息,比如在STA线程上调用时避免死锁情况

在我的情况下,我相信以下情况正在发生:In-proc STA COM对象A对out-of-proc对象B进行调用,然后期望B通过回调作为传出调用的一部分

以简化形式:

var result = await Task.Factory.StartNew(() =>
{
    // in-proc object A
    var a = new A(); 
    // out-of-proc object B
    var b = new B(); 
    // A calls B and B calls back A during the Method call
    return a.Method(b);     
}, CancellationToken.None, TaskCreationOptions.None, staTaskScheduler);
问题是,
a.Method(b)
永远不会返回。据我所知,之所以会发生这种情况,是因为
BlockingCollection
中的阻塞等待不会传递消息,所以我对引用语句的假设可能是错误的

编辑的在测试WinForms应用程序的UI线程上执行时,相同的代码可以工作(即,提供
TaskScheduler.FromCurrentSynchronizationContext()
而不是
staTaskScheduler
Task.Factory.StartNew

解决这个问题的正确方法是什么?我是否应该实现一个自定义同步上下文,该上下文将显式地使用
CoWaitForMultipleHandles
泵送消息,并将其安装在由
StaTaskScheduler
启动的每个STA线程上

如果是的话,
BlockingCollection
的底层实现会调用我的方法吗?是否可以使用来实现同步上下文。等待?
编辑了,其中一些代码显示,在执行阻塞等待时,托管STA线程不会泵送。代码是一个完整的控制台应用程序,可供复制/粘贴/运行:

using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleTestApp
{
    class Program
    {
        // start and run an STA thread
        static void RunStaThread(bool pump)
        {
            // test a blocking wait with BlockingCollection.Take
            var tasks = new BlockingCollection<Task>();

            var thread = new Thread(() => 
            {
                // Create a simple Win32 window 
                var hwndStatic = NativeMethods.CreateWindowEx(0, "Static", String.Empty, NativeMethods.WS_POPUP,
                    0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);

                // subclass it with a custom WndProc
                IntPtr prevWndProc = IntPtr.Zero;

                var newWndProc = new NativeMethods.WndProc((hwnd, msg, wParam, lParam) =>
                {
                    if (msg == NativeMethods.WM_TEST)
                        Console.WriteLine("WM_TEST processed");
                    return NativeMethods.CallWindowProc(prevWndProc, hwnd, msg, wParam, lParam);
                });

                prevWndProc = NativeMethods.SetWindowLong(hwndStatic, NativeMethods.GWL_WNDPROC, newWndProc);
                if (prevWndProc == IntPtr.Zero)
                    throw new ApplicationException();

                // post a test WM_TEST message to it
                NativeMethods.PostMessage(hwndStatic, NativeMethods.WM_TEST, IntPtr.Zero, IntPtr.Zero);

                // BlockingCollection blocks without pumping, NativeMethods.WM_TEST never arrives
                try { var task = tasks.Take(); }
                catch (Exception e) { Console.WriteLine(e.Message); }

                if (pump)
                {
                    // NativeMethods.WM_TEST will arrive, because Win32 MessageBox pumps
                    Console.WriteLine("Now start pumping...");
                    NativeMethods.MessageBox(IntPtr.Zero, "Pumping messages, press OK to stop...", String.Empty, 0);
                }
            });

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

            Thread.Sleep(2000);

            // this causes the STA thread to end
            tasks.CompleteAdding(); 

            thread.Join();
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Testing without pumping...");
            RunStaThread(false);

            Console.WriteLine("\nTest with pumping...");
            RunStaThread(true);

            Console.WriteLine("Press Enter to exit");
            Console.ReadLine();
        }
    }

    // Interop
    static class NativeMethods
    {
        [DllImport("user32")]
        public static extern IntPtr SetWindowLong(IntPtr hwnd, int nIndex, WndProc newProc);

        [DllImport("user32")]
        public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hwnd, int msg, int wParam, int lParam);

        [DllImport("user32.dll")]
        public static extern IntPtr CreateWindowEx(int dwExStyle, string lpClassName, string lpWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);

        [DllImport("user32.dll")]
        public static extern bool PostMessage(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        public static extern int MessageBox(IntPtr hwnd, string text, String caption, int options);

        public delegate IntPtr WndProc(IntPtr hwnd, int msg, int wParam, int lParam);

        public const int GWL_WNDPROC = -4;
        public const int WS_POPUP = unchecked((int)0x80000000);
        public const int WM_USER = 0x0400;

        public const int WM_TEST = WM_USER + 1;
    }
}
使用系统;
使用System.Collections.Concurrent;
使用System.Runtime.InteropServices;
使用系统线程;
使用System.Threading.Tasks;
名称空间控制台STAPP
{
班级计划
{
//启动并运行STA线程
静态空心螺纹(布尔泵)
{
//使用BlockingCollection测试阻塞等待。采取
var tasks=new BlockingCollection();
变量线程=新线程(()=>
{
//创建一个简单的Win32窗口
var hwndStatic=NativeMethods.CreateWindowEx(0,“Static”,String.Empty,NativeMethods.WS_弹出,
0,0,0,0,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero);
//使用自定义WndProc对其进行子类化
IntPtr prevWndProc=IntPtr.Zero;
var newWndProc=newnativemethods.WndProc((hwnd、msg、wParam、lParam)=>
{
if(msg==NativeMethods.WM_测试)
控制台写入线(“WM_测试已处理”);
返回NativeMethods.CallWindowProc(prevWndProc、hwnd、msg、wParam、lParam);
});
prevWndProc=NativeMethods.SetWindowLong(hwndStatic,NativeMethods.GWL_WNDPROC,newWndProc);
如果(prevWndProc==IntPtr.Zero)
抛出新的ApplicationException();
//向其发布测试WM_测试消息
NativeMethods.PostMessage(hwndStatic,NativeMethods.WM_测试,IntPtr.Zero,IntPtr.Zero);
//在没有泵送的情况下阻塞收集块,NativeMethods.WM_测试永远不会到达
试试{var task=tasks.Take();}
catch(异常e){Console.WriteLine(e.Message);}
if(泵)
{
//NativeMethods.WM_测试将到达,因为Win32 MessageBox
控制台。WriteLine(“现在开始泵送…”);
NativeMethods.MessageBox(IntPtr.Zero,“泵送消息,按OK停止…”,String.Empty,0);
}
});
SetApartmentState(ApartmentState.STA);
thread.Start();
《睡眠》(2000年);
//这将导致STA线程结束
tasks.CompleteAdding();
thread.Join();
}
静态void Main(字符串[]参数)
{
控制台。WriteLine(“无泵测试…”);
RunStaThread(false);
Console.WriteLine(“\n测试泵送…”);
RunStaThread(true);
控制台写入线(“按回车键退出”);
Console.ReadLine();
}
}
//互操作
静态类NativeMethods
{
[DllImport(“user32”)]
公共静态外部IntPtr SetWindowLong(IntPtr hwnd、int nIndex、WndProc newProc);
[DllImport(“user32”)]
公共静态外部IntPtr CallWindowProc(IntPtr lpPrevWndFunc、IntPtr hwnd、intmsg、intwparam、intlparam);
[DllImport(“user32.dll”)]
公共静态外部IntPtr CreateWindowEx(int dwExStyle、字符串lpClassName、字符串lpWindowName、int dwStyle、int x、int y、int nWidth、int nHeight、IntPtr hWndParent、IntPtr HNMENU、IntPtr hInstance、IntPtr lpParam);
[DllImport(“user32.dll”)]
公共静态外部bool PostMessage(IntPtr hwnd、uint msg、IntPtr wParam、IntPtr lParam);
[DllImport(“user32.dll”)]
公共静态外部int MessageBox(IntPtr hwnd、字符串文本、字符串标题、int选项);
公共委托IntPtr WndProc(IntPtr hwnd、int msg、int wParam、int lParam);
公共建筑国际GWL_WNDPROC=-4;
public const int WS_POPUP=unchecked((int)0x8000000);
public const int WM_USER=0x0400;
public const int WM_TEST=WM_USER+1;
}
}
这就产生了 Testing without pumping... The collection argument is empty and has been marked as complete with regards to additions. Test with pumping... The collection argument is empty and has been marked as complete with regards to additions. Now start pumping... WM_TEST processed Press Enter to exit
user32.dll!PeekMessageW()   Unknown
combase.dll!CCliModalLoop::MyPeekMessage(tagMSG * pMsg, HWND__ * hwnd, unsigned int min, unsigned int max, unsigned short wFlag) Line 2305  C++
combase.dll!CCliModalLoop::PeekRPCAndDDEMessage() Line 2008 C++
combase.dll!CCliModalLoop::FindMessage(unsigned long dwStatus) Line 2087    C++
combase.dll!CCliModalLoop::HandleWakeForMsg() Line 1707 C++
combase.dll!CCliModalLoop::BlockFn(void * * ahEvent, unsigned long cEvents, unsigned long * lpdwSignaled) Line 1645 C++
combase.dll!ClassicSTAThreadWaitForHandles(unsigned long dwFlags, unsigned long dwTimeout, unsigned long cHandles, void * * pHandles, unsigned long * pdwIndex) Line 46 C++
combase.dll!CoWaitForMultipleHandles(unsigned long dwFlags, unsigned long dwTimeout, unsigned long cHandles, void * * pHandles, unsigned long * lpdwindex) Line 120 C++
clr.dll!MsgWaitHelper(int,void * *,int,unsigned long,int)   Unknown
clr.dll!Thread::DoAppropriateWaitWorker(int,void * *,int,unsigned long,enum WaitMode)   Unknown
clr.dll!Thread::DoAppropriateWait(int,void * *,int,unsigned long,enum WaitMode,struct PendingSync *)    Unknown
clr.dll!CLREventBase::WaitEx(unsigned long,enum WaitMode,struct PendingSync *)  Unknown
clr.dll!CLREventBase::Wait(unsigned long,int,struct PendingSync *)  Unknown
clr.dll!Thread::Block(int,struct PendingSync *) Unknown
clr.dll!SyncBlock::Wait(int,int)    Unknown
clr.dll!ObjectNative::WaitTimeout(bool,int,class Object *)  Unknown
class MySynchronizationProvider : System.Threading.SynchronizationContext {
    public MySynchronizationProvider() {
        base.SetWaitNotificationRequired();
    }
    public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) {
        for (; ; ) {
            int result = MsgWaitForMultipleObjects(waitHandles.Length, waitHandles, waitAll, millisecondsTimeout, 8);
            if (result == waitHandles.Length) System.Windows.Forms.Application.DoEvents();
            else return result;
        }
    }
    [DllImport("user32.dll")]
    private static extern int MsgWaitForMultipleObjects(int cnt, IntPtr[] waitHandles, bool waitAll,
        int millisecondTimeout, int mask);        
}
    System.ComponentModel.AsyncOperationManager.SynchronizationContext =
        new MySynchronizationProvider();
// the core loop
var 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;
}
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);
}
Initial thread #9 On STA thread #10 Post some WM_TEST messages... Press Enter to continue... WM_TEST processed: 1 WM_TEST processed: 2 WM_TEST processed: 3 After await, thread #10 Pending messages in the queue: False Exiting STA thread #10 Current thread #12 Press any key to exit