title: StaTaskScheduler and STA thread message pumping
body: <p>TL;DR: <em>A deadlock inside a task run by <code>StaTaskScheduler</code>.</em> Long version:</p>
  
  <p>I'm using <a href="http://blogs.msdn.com/b/pfxteam/archive/2010/04/07/9990421.aspx"><code>StaTaskScheduler</code></a> from <a href="http://code.msdn.microsoft.com/ParExtSamples">ParallelExtensionsExtras</a> by Parallel Team, to host some legacy STA COM objects supplied by a third party. The description of the <code>StaTaskScheduler</code> implementation details says the following: </p>
  
  <blockquote>
    <p>The good news is that TPL’s implementation is able to run on either
    MTA or STA threads, and takes into account relevant differences around
    underlying APIs like WaitHandle.WaitAll (which only supports MTA
    threads when the method is provided multiple wait handles).</p>
  </blockquote>
  
  <p>I thought that would mean the blocking parts of TPL would use a wait API which pumps messages, like <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms680732%28v=vs.85%29.aspx"><code>CoWaitForMultipleHandles</code></a>, to avoid deadlock situations when called on an STA thread.</p>
  
  <p>In my situation, I believe the following is happening: in-proc STA COM object A makes a call to out-of-proc object B, then expects a callback from B via as a part of the outgoing call.</p>
  
  <p>In a simplified form:</p>
  
  <pre><code>var result = await Task.Factory.StartNew(() =&gt;
  {
      // 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);
  </code></pre>
  
  <p>The problem is, <code>a.Method(b)</code> never returns. As far as I can tell, this happens because a blocking wait somewhere inside <code>BlockingCollection&lt;Task&gt;</code> does not pump messages, so my assumption about the quoted statement is probably wrong. </p>
  
  <p><strong>EDITED</strong> The same code works when is executed on the UI thread of the test WinForms application (that is, providing <code>TaskScheduler.FromCurrentSynchronizationContext()</code> instead of <code>staTaskScheduler</code> to <code>Task.Factory.StartNew</code>). </p>
  
  <p>What is the right way to solve this? Should I implemented a custom synchronization context, which would explicitly pump messages with <code>CoWaitForMultipleHandles</code>, and install it on each STA thread started by <code>StaTaskScheduler</code>? </p>
  
  <p>If so, will the underlying implementation of <code>BlockingCollection</code> be calling my <a href="http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.wait%28v=vs.110%29.aspx"><code>SynchronizationContext.Wait</code></a> method? Can I use <a href="http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.waithelper%28v=vs.110%29.aspx"><code>SynchronizationContext.WaitHelper</code></a> to implement <code>SynchronizationContext.Wait</code>?
  <hr>
  <strong>EDITED</strong> with some code showing that a managed STA thread doesn't pump when doing a blocking wait. The code is a complete console app ready for copy/paste/run:</p>
  
  <pre><code>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&lt;Task&gt;();
  
              var thread = new Thread(() =&gt; 
              {
                  // 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) =&gt;
                  {
                      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;
      }
  }
  </code></pre>
  
  <p>This produces the output:</p>
  
  <pre>
  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
  </pre>