StaTaskScheduler и прокачка сообщений потока STA

TL; DR:Тупик внутри задачи, которой управляет.StaTaskScheduler Длинная версия: я

м используюStaTaskScheduler отParallelExtensionsExtras Parallel Team, для размещения некоторых устаревших COM-объектов STA, предоставленных третьей стороной. ОписаниеStaTaskScheduler Детали реализации говорят о следующем:

Хорошей новостью является то, что TPLРеализация может работать в потоках MTA или STA и учитывает соответствующие различия между базовыми API-интерфейсами, такими как WaitHandle.WaitAll (который поддерживает потоки MTA только тогда, когда метод имеет несколько дескрипторов ожидания).

Я думал, что это будет означать, что блокирующие части TPL будут использовать API ожидания, который качает сообщения, какCoWaitForMultipleHandles, чтобы избежать тупиковых ситуаций при вызове в потоке STA.

В моей ситуации, я полагаю, происходит следующее: COM-объект A STA in-proc выполняет вызов к объекту B вне-proc, а затем ожидает обратный вызов от B via как часть исходящего вызова.

В упрощенном виде:

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 (т.е.TaskScheduler.FromCurrentSynchronizationContext() вместоstaTaskScheduler вTask.Factory.StartNew).

Как правильно решить эту проблему? Должен ли я реализовать пользовательский контекст синхронизации, который будет явно качать сообщения сCoWaitForMultipleHandlesи установить его на каждый поток STA, запущенный?StaTaskScheduler

Если да, будет ли основная реализацияBlockingCollection звоню моемуSynchronizationContext.Wait метод? Могу ли я использоватьSynchronizationContext.WaitHelper реализовать?SynchronizationContext.Wait

РЕДАКТИРОВАНИЕ с некоторым кодом, показывающим, что управляемый поток 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();

            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;
    }
}

Это производит вывод:

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

Ответы на вопрос(2)

Ваш ответ на вопрос