BlockingCollection
ブロッキング中に実際にポンプします。次の質問に答えているときに、STA ポンピングに関する興味深い詳細がいくつかあることを知りました。
StaTaskScheduler および STA スレッド メッセージのポンピング
ただし、リストした他の API と同じように、非常に限定された未公開の COM 固有のメッセージ セットを送り出します。汎用の Win32 メッセージは送信されません (特殊なケースはWM_TIMER
であり、これもディスパッチされません)。これは、フル機能のメッセージ ループを予期する一部の STA COM オブジェクトでは問題になる可能性があります。
これを試してみたい場合は、独自のバージョンの を作成しSynchronizationContext
、 overrideSynchronizationContext.Wait
を呼び出しSetWaitNotificationRequired
て、カスタムの同期コンテキスト オブジェクトを STA スレッドにインストールします。次に、内部にブレークポイントを設定し、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));
};
}
}
タスクの完了には 2000 ミリ秒しかかかりませんが、メッセージ ボックスには 4000 ミリ秒までの経過時間が表示されます。
これは、継続コールバックが を介してスケジュールされているために発生await
します。このメッセージはポンピングされず、タイムアウトします。WindowsFormsSynchronizationContext.Post
Control.BeginInvoke
PostMessage
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);
}
}
}
}
元のコードを次のように変更します。
var startTick = Environment.TickCount;
handle.WaitOneAndPump(4000);
MessageBox.Show("Lapse: " + (Environment.TickCount - startTick));
await
継続メッセージが によってポンピングされApplication.DoEvents()
、タスクが完了し、そのハンドルが通知されるため、経過時間は約 2000 ミリ秒になります。
とは言っても、本番コードのようなものを使用することはお勧めしませんWaitOneAndPump
(ごく少数の特定のケースを除いて)。これは、UI の再入可能性など、さまざまな問題の原因となります。これらの問題が、Microsoft が標準のポンピング動作を、COM マーシャリングに不可欠な特定の COM 固有のメッセージのみに限定した理由です。