考虑对WaitOne进行两次调用。第一次调用的超时时间为零并返回一个布尔值,它会告诉您是否获得了信号量,或者其他人是否仍然拥有它。从那里可能会发生两件事:
1)如果其他人拥有它,弹出一条消息说“其他人拥有信号量”并再次调用WaitOne,但没有超时(就像你现在正在做的那样)。对 WaitOne 的第二次调用返回后,关闭一秒钟前弹出的窗口。
2) 如果您对 0 超时的 waitOne 调用返回 true,那么您在第一次尝试时就获得了信号量。无需弹出窗口。
例子:
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()
如果在执行第二次等待时挂起 GUI 线程,您可能看不到结果。考虑在第二个线程上进行长时间等待,并在拥有信号量后在 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 可能应该采用一些参数来跟踪传递到每个步骤的状态。也没有办法中止等待(超时)。它可能会起作用,但不应将其放入任何生产代码中,因为它太难以维护或扩展。
如果你真的想把它做对,你应该考虑将 ATM 逻辑封装在一个对象中,该对象将引发 GUI 可以订阅的事件。你可以有一个ATM.LogInAsync(Account,Pin)
可以调用的方法。此方法将立即返回,但一段时间后,ATM 类上的一个事件(如“LogInComplete”)将触发。此事件将有一个自定义 EventArgs 对象,该对象将包含用于跟踪发生了哪些登录的数据(主要是帐号)。这称为基于事件的异步模式
或者,如果您使用的是 C# 5.0,则可以在方法中使用新的Async/Await语法AcquireSemaphoreAndGenerateCallback()
。这可能是最简单的方法,因为编译器将为您处理大部分复杂性