其中阻塞操作导致STA线程泵COM消息? [英] Which blocking operations cause an STA thread to pump COM messages?

查看:207
本文介绍了其中阻塞操作导致STA线程泵COM消息?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当一个COM对象在STA线程实例化,线程通常实行以通话编组来来回回其他线程的消息泵(见的这里)。

When a COM object is instantiated on an STA thread, the thread usually has to implement a message pump in order to marshal calls to and fro other threads (see here).

一个既可以泵手动的消息,还是要靠在等待一些,但不是全部,线程阻塞操作会自动泵COM相关的消息的事实。文档通常不会在决定哪个是哪个了帮助(请参见此相关的问题)。

One can either pump messages manually, or rely on the fact that some, but not all, thread-blocking operations will automatically pump COM-related messages while waiting. The documentation often doesn't help in deciding which is which (see this related question).

我怎么能确定一个线程阻塞操作将泵上的STA?

How can I determine if a thread-blocking operation will pump COM messages on an STA?

部分列出了目前为止:

阻止它的操作做泵*:

  • Thread.Join
  • WaitHandle.WaitOne/WaitAny/WaitAll (WaitAll cannot be called from an STA thread though)
  • GC.WaitForPendingFinalizers
  • Monitor.Enter (and therefore lock) - under some conditions
  • ReaderWriterLock
  • BlockingCollection

禁止该的操作没有泵:

  • Thread.Sleep
  • Console.ReadKey (read it somewhere)

*注意: Noseratio的回答说,即使操作里面做泵,一个未公开的非常有限的COM-这样做具体的消息。

*Note Noseratio's answer saying that even operations which do pump, do so for a very limited undisclosed set of COM-specific messages.

推荐答案

BlockingCollection 的确会阻塞时泵。我了解到,在回答以下问题,其中有大约STA抽了一些有趣的细节:

BlockingCollection will indeed pump while blocking. I've learnt that while answering the following question, which has some interesting details about STA pumping:

StaTaskScheduler和STA线程消息抽水

然而,将一个泵未公开非常有限特定的COM的消息上,和你一样列出的其他的API。它不会泵通用的Win32消息(一个特殊情况是 WM_TIMER ,它也不会被派遣)。这可能是一个问题而期望一个全功能的消息循环,一些STA COM对象。

However, it will pump a very limited undisclosed set of COM-specific messages, same as the other APIs you listed. It won't pump general purpose Win32 messages (a special case is WM_TIMER, which won't be dispatched either). This might be a problem for some STA COM objects which expect a full-featured message loop.

如果你喜欢这个实验,创建自己的的SynchronizationContext 的版本,覆盖<一个href=\"http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.wait%28v=vs.110%29.aspx\"相对=nofollow> SynchronizationContext.Wait ,叫<一个href=\"http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.setwaitnotificationrequired%28v=vs.110%29.aspx\"相对=nofollow> SetWaitNotificationRequired 和STA线程安装自定义同步上下文对象。然后设置里面断点等待,看看会做出什么的API它被调用。

If you like to experiment with this, create your own version of SynchronizationContext, override SynchronizationContext.Wait, call SetWaitNotificationRequired and install your custom synchronization context object on an STA thread. Then set a breakpoint inside Wait and see what APIs will make it get called.

在多大程度上的WaitOne 标准抽行为实际上是有限的?下面是导致在UI线程死锁的典型例子。我在这里使用的WinForms,但同样关注适用于WPF:

To what extent the standard pumping behavior of WaitOne is actually limited? Below is a typical example causing a deadlock on the UI thread. I use WinForms here, but the same concern applies to WPF:

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();

        this.Load += (s, e) =>
        {
            Func<Task> doAsync = async () =>
            {
                await Task.Delay(2000);
            };

            var task = doAsync();
            var handle = ((IAsyncResult)task).AsyncWaitHandle;

            var startTick = Environment.TickCount;
            handle.WaitOne(4000);
            MessageBox.Show("Lapse: " + (Environment.TickCount - startTick));
        };
    }
}

消息框将显示〜4000毫秒的响应时间,虽然任务只需要2000毫秒才能完成。

The message box will show the time lapse of ~ 4000 ms, although the task takes only 2000 ms to complete.

这是因为在的await 延续回调是通过 WindowsFormsSynchronizationContext.Post 计划,它使用 Control.BeginInvoke ,从而使用 PostMessage的,张贴,注册一个普通的Windows消息 RegisterWindowMessage 。此消息没有得到泵和 handle.WaitOne 超时。

That happens because the await continuation callback is scheduled via WindowsFormsSynchronizationContext.Post, which uses Control.BeginInvoke, which in turn uses PostMessage, posting a regular Windows message registered with RegisterWindowMessage. This message doesn't get pumped and handle.WaitOne times out.

如果我们使用 handle.WaitOne(Timeout.Infinite),我们就会有一个经典的僵局。

If we used handle.WaitOne(Timeout.Infinite), we'd have a classic deadlock.

现在让我们来实现具有明确的抽版本的WaitOne 的(并称之为 WaitOneAndPump

Now let's implement a version of WaitOne with explicit pumping (and call it WaitOneAndPump):

public static bool WaitOneAndPump(
    this WaitHandle handle, int millisecondsTimeout)
{
    var startTick = Environment.TickCount;
    var handles = new[] { handle.SafeWaitHandle.DangerousGetHandle() };

    while (true)
    {
        // wait for the handle or a message
        var timeout = (uint)(Timeout.Infinite == millisecondsTimeout ?
                Timeout.Infinite :
                Math.Max(0, millisecondsTimeout + 
                    startTick - Environment.TickCount));

        var result = MsgWaitForMultipleObjectsEx(
            1, handles,
            timeout,
            QS_ALLINPUT,
            MWMO_INPUTAVAILABLE);

        if (result == WAIT_OBJECT_0)
            return true; // handle signalled
        else if (result == WAIT_TIMEOUT)
            return false; // timed-out
        else if (result == WAIT_ABANDONED_0)
            throw new AbandonedMutexException(-1, handle);
        else if (result != WAIT_OBJECT_0 + 1)
            throw new InvalidOperationException();
        else
        {
            // a message is pending 
            if (timeout == 0)
                return false; // timed-out
            else
            {
                // do the pumping
                Application.DoEvents();
                // no more messages, raise Idle event
                Application.RaiseIdle(EventArgs.Empty);
            }
        }
    }
}

和改变原来的code是这样的:

And change the original code like this:

var startTick = Environment.TickCount;
handle.WaitOneAndPump(4000);
MessageBox.Show("Lapse: " + (Environment.TickCount - startTick));

在时间的推移,现在将是〜2000毫秒,因为的await 连续消息得到由抽Application.DoEvents(),任务完成和手柄信号。

The time lapse now will be ~2000 ms, because the await continuation message gets pumped by Application.DoEvents(), the task completes and its handle is signaled.

这表示,我从来没有建议使用像 WaitOneAndPump 生产code (除了极少数特殊情况下)。它像UI重入各种问题的根源。这些问题的原因微软限制了标准激发行为只有某些特定的COM-消息,COM封送处理是至关重要的。

That said, I'd never recommend using something like WaitOneAndPump for production code (besides for very few specific cases). It's a source of various problems like UI re-entrancy. Those problems are the reason Microsoft has limited the standard pumping behavior to only certain COM-specific messages, vital for COM marshaling.

这篇关于其中阻塞操作导致STA线程泵COM消息?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆