2016-05-01 9 views
0

私のアプリケーションでは、キューに変更があるたびに通知を発行するキューを持っていますが、キューイベントハンドラが同時に複数回発生すると、スレッドセーフなイベントハンドラを作成する方法

private async void NotificationQueue_Changed(object sender, EventArgs e) 
{ 
    if (!IsQueueInProcess) 
    await ProcessQeueue(); 
} 

ProcessQueue方法では、私がtrueにIsQueueInProcessを設定していますし、いつでもそれを完了します:それは、以下

は、イベントハンドラのコードです...大丈夫だが、私が望んでいないこと、ですfalseに設定されます。今、問題は、複数のイベント通知が同時に発生するたびに、複数の ProcessQeueueメソッドが実行を開始するということです。これは望ましくありません。私はいつでも ProcessQeueueの実行が1つしかないことを確認したいと思います。

+0

の同時実行性の制限があり、それ天気を伝える別のブール値は、現在実行されていることがありますか? –

+0

[this](https://support.microsoft.com/en-us/kb/816161)サポートページをご覧ください。 –

+0

@zee IsQueueInProcessを静的にしてみてください。 – Ilan

答えて

1

このイベントは、キューの変更が発生したときに発生し、同時にキューを使用できる(つまり、複数のプロデューサがキューに追加する)場合は、これに対処する方法は、イベントベースの動作を完全に放棄することです。その代わりに、を使用して、GetConsumingEnumerable()によってキューを処理する専用のスレッドを使用します。このメソッドは、キューが空である限りスレッドをブロックし、他のスレッドが何かを追加するたびにスレッドがキュー内の項目を削除および処理できるようにします。コレクション自体はスレッドセーフなので、追加のスレッド同期(キューの処理、つまり…アイテムの処理はスレッドのやりとりを伴う可能性がありますが、そのアスペクトを記述する質問は何もありません。だから私はそれについて何か他の何かを言うことができない)。

private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1); 

private async void NotificationQueue_Changed(object sender, EventArgs e) 
{ 
    if (_semaphore.Wait(0)) 
    { 
     await ProcessQueue(); 
     _semaphore.Release(); 
    } 
} 

セマフォのロックを獲得するために、上記の試み:文字通り質問をとる、前記

、最も簡単な方法は、セマフォを含むことであろう。タイムアウトが0ミリ秒の場合、セマフォを取得できなかった場合でもすぐに戻ります。戻り値は、セマフォが正常に取得されたかどうかを示します。このようにして

は、限り、未解決のキュー処理動作がないので、現在のイベントハンドラの呼び出しは、セマフォを獲得することができ、ProcessQueue()メソッドを呼び出します。その操作が完了すると、継続によってセマフォが解放されます。それが起こるまで、イベントハンドラの他の呼び出しはセマフォを獲得することができず、したがってキューの処理を開始しません。


ここでは、キューが常に空であるか、または常に処理操作が実行されることを確実にする競合スレッドの解決策を保証するものはありません。これはあなた次第です。つまり、ProcessQueue()メソッドが、スレッドがキューを変更してこのイベントを発生させた場合、そのスレッドが最初のラウンドではない別の処理を開始しないことを保証するために必要な同期化が必要です。その変化を観察することができます。

または、イベントを発生させるスレッドに対して、現在の処理操作によってキューへの変更が観察されるか、スレッドが新しいイベントを開始するかどうかを確認する必要があります。

あなたの質問には、その懸念事項に具体的に対処するための十分なコンテキストがありません。私は、この種のシステムを実装しようとするときに誰かが見過ごしてしまうのが一般的なことだと指摘します。IMHO、キューに追加された要素を消費するためにBlockingCollection<T>を使用している専用のスレッドを持つより多くの理由。 :)


How to avoid reentrancy with async void event handlers?も参照してください。これは、少し異なる質問です。受け入れられた答えは、イベントハンドラの各呼び出しによってイベントハンドラによって開始されたオペレーションを引き起こします。新しいオペレーションの開始をスキップしたいだけなので、シナリオは簡単ですが、そこにはいくつかの有用な洞察があります。

1

私は、イベントベースの通知を放棄することが最良の解決策であり、プロデューサ/コンシューマキューに移動する必要があることに同意します。しかし、BlockingCollection<T>ではなく、TPL Dataflowブロックの1つを推奨します。

特に、ActionBlock<T>は非常にうまく動作するはずです:デフォルトでは

private readonly ActionBlock<T> notificationQueue = new ActionBlock<T>(async t => 
{ 
    await ProcessQueueItem(t); 
}); 

は、TPLデータフローブロックは1

関連する問題