WaitOneを 2 回呼び出すことを検討してください。最初の呼び出しはタイムアウトがゼロで、セマフォを取得したかどうか、または他の誰かがまだセマフォを所有しているかどうかを示す bool を返します。そこから次の 2 つのことが起こります。
1) 他の誰かがセマフォを所有している場合は、「誰かがセマフォを所有しています」というメッセージをポップアップ表示し、WaitOneを再度呼び出しますが、タイムアウトはありません (現在行っているように)。WaitOne への 2 回目の呼び出しが返されたら、1 秒前にポップアップしたウィンドウを閉じます。
2) タイムアウト 0 での waitOne の呼び出しが true を返す場合、1 回目の試行でセマフォを取得しています。ウィンドウをポップアップする必要はありません。
例:
if( semaphore.WaitOne(0) ) //This returns immediately
{
//We own the semaphore now.
DoWhateverYouNeedToDo();
}
else
{
//Looks like someone else already owns the semaphore.
PopUpNotification();
semaphore.WaitOne(); //This one will block until the semaphore is available
DoWhateverYouNeedToDo();
CloseNotification();
}
semaphore.Release();
ここには他にもいくつかの問題が潜んでいることに注意してください。
- おそらく、try/finally ブロックを使用してセマフォを解放し、すべての例外パスでセマフォが確実に解放されるようにする必要があります。
- アプリケーションが待機中に応答しなくなるため、GUI スレッドから semaphore.WaitOne() を呼び出すこともおそらく悪い考えです。実際、
PopUpNotification()
2 回目の待機中に GUI スレッドをハングさせた場合、結果が表示されない場合があります。セマフォを所有したら、2 番目のスレッドで長時間待機し、GUI スレッドでイベントを発生させることを検討してください。
問題 2 を解決するには、次の設計を検討してください。
private void button1_Click(object sender, EventArgs e)
{
if(AcquireSemaphoreAndGenerateCallback())
{
//Semaphore was acquired right away. Go ahead and do whatever we need to do
DoWhateverYouNeedToDo();
semaphore.Release()
}
else
{
//Semaphore was not acquired right away. Callback will occur in a bit
//Because we're not blocking the GUI thread, this text will appear right away
textBox1.Text = "Waiting on the Semaphore!";
//Notice that the method returns right here, so the GUI will be able to redraw itself
}
}
//This method will either acquire the semaphore right away and return true, or
//have a worker thread wait on the semaphore and return false. In the 2nd case,
//"CallbackMethod" will run on the GUI thread once the semaphore has been acquired
private void AcquireSemaphoreAndGenerateCallback()
{
if( semaphore.WaitOne(0) ) //This returns immediately
{
return true; //We have the semaphore and didn't have to wait!
}
else
{
ThreadPool.QueueUserWorkItem(new WaitCallback(Waiter));
return false; //Indicate that we didn't acquire right away
}
}
//Wait on the semaphore and invoke "CallbackMethod" once we own it. This method
//is meant to run on a background thread.
private void Waiter(object unused)
{
//This is running on a separate thread
Semaphore.WaitOne(); //Could take a while
//Because we're running on a separate thread, we need to use "BeginInvoke" so
//that the method we're calling runs on the GUI thread
this.BeginInvoke(new Action(CallbackMethod));
}
private void CallbackMethod()
{
textBox1.Text = string.Empty; //Get rid of the "Waiting For Semaphore" text. Can't do this if we're not running on the GUI thread
DoWhateverYouNeedToDo();
semaphore.Release();
}
さて、このソリューションにも危険が伴う可能性があります。メソッドからメソッドへとジャンプするため、プログラムの実行を追跡するのはちょっと難しいです。例外が発生した場合、回復が難しく、プログラムの状態がすべて正しいことを確認することが困難になる可能性があります。また、これらすべてのメソッド呼び出しを通じて、口座番号や暗証番号などを追跡する必要があります。そのためには、Waiter と CallbackMethod が、各ステップに渡されるこの状態を追跡する何らかのパラメーターを取る必要があります。待機を中止する方法もありません (タイムアウト)。おそらく機能しますが、保守や拡張が難しすぎるため、製品コードにするべきではありません。
本当に正しく実行したい場合は、GUI がサブスクライブできるイベントを発生させるオブジェクトに ATM ロジックをカプセル化することを検討する必要があります。そのようなメソッドATM.LogInAsync(Account,Pin)
を呼び出すことができます。このメソッドはすぐに戻りますが、しばらくしてから、「LogInComplete」などの ATM クラスのイベントが発生します。このイベントには、どのログインが発生したかを追跡するためのデータ (主にアカウント番号) を含むカスタム EventArgs オブジェクトがあります。これは、イベントベースの非同期パターンと呼ばれます
または、C# 5.0 を使用している場合は、メソッドで新しいAsync/Await構文を使用できますAcquireSemaphoreAndGenerateCallback()
。複雑な作業のほとんどはコンパイラが処理してくれるので、おそらくこれが最も簡単な方法です。