bav*_*aza 17 c# com interop message-pump
当一个COM对象在STA线程上实例化时,该线程通常必须实现一个消息泵,以便为来回调用其他线程(见这里).
可以手动泵送消息,或者依赖于某些(但不是全部)线程阻塞操作在等待时自动泵送COM相关消息的事实.文档通常无助于决定哪个是哪个(参见相关问题).
如何确定线程阻塞操作是否会在STA上泵送COM消息?
到目前为止的部分清单:
阻断其业务做泵*:
Thread.Join
WaitHandle.WaitOne
/ WaitAny
/WaitAll
(WaitAll
不能从一个STA线程虽然称为)GC.WaitForPendingFinalizers
Monitor.Enter
(因此lock
) - 在某些条件下ReaderWriterLock
阻止不泵送的操作:
Thread.Sleep
Console.ReadKey
(在某处读)*注意Noseratio的答案说,即使是操作泵,也是为非常有限的未公开的COM特定消息集.
BlockingCollection
确实会阻塞.我已经了解到,在回答以下问题时,其中有一些有关STA抽吸的有趣细节:
但是,它将提供一组非常有限的未公开的COM特定消息,与您列出的其他API相同.它不会抽取通用的Win32消息(特殊情况是WM_TIMER
,也不会被调度).对于某些需要全功能消息循环的STA COM对象,这可能是一个问题.
如果您想试验这一点,请在STA线程上创建自己的版本SynchronizationContext
,覆盖SynchronizationContext.Wait
,调用SetWaitNotificationRequired
和安装自定义同步上下文对象.然后在里面设置一个断点Wait
,看看会调用哪些API.
标准泵送行为在多大程度上WaitOne
实际上受到限制?下面是导致UI线程死锁的典型示例.我在这里使用WinForms,但同样的问题适用于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));
};
}
}
Run Code Online (Sandbox Code Playgroud)
消息框将显示~4000 ms的时间间隔,但任务只需2000 ms即可完成.
之所以发生这种情况,是因为await
继续回调是通过调度来进行的WindowsFormsSynchronizationContext.Post
,后者使用Control.BeginInvoke
,然后使用PostMessage
,发布注册的常规Windows消息RegisterWindowMessage
.此消息不会被抽出并handle.WaitOne
超时.
如果我们使用handle.WaitOne(Timeout.Infinite)
,我们就会遇到经典的僵局.
现在让我们实现一个WaitOne
带有显式泵浦的版本(并调用它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);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
并像这样更改原始代码:
var startTick = Environment.TickCount;
handle.WaitOneAndPump(4000);
MessageBox.Show("Lapse: " + (Environment.TickCount - startTick));
Run Code Online (Sandbox Code Playgroud)
现在时间流逝将是~2000毫秒,因为await
连续消息被抽出Application.DoEvents()
,任务完成并且其信号被发出信号.
也就是说,我从不建议使用像WaitOneAndPump
生产代码那样的东西(除了极少数特定情况).它是UI重入等各种问题的根源.这些问题是微软将标准泵送行为仅限于特定COM特定消息的原因,这对于COM编组至关重要.
归档时间: |
|
查看次数: |
3534 次 |
最近记录: |