执行结果如下:
Starting with timeout of 5 seconds
Operation started
Starting with timeout of 5 but cancelling after 2 seconds
Finished...
现在当我们停止操作时,定时器被丢弃,这样就避免了再次执行操作。这已经实现了我们最初的想法,当然还有另一种方式来处理这个问题。
解决方案3:ManualResetEvent或AutoResetEventManualResetEvent/AutoResetEvent的字面意思是手动或自动重置事件。AutoResetEvent和ManualResetEvent是帮助您处理多线程通信的类。 基本思想是一个线程可以一直等待,知道另一个线程完成某个操作, 然后等待的线程可以“释放”并继续运行。
ManualResetEvent类和AutoResetEvent类请参阅MSDN:
ManualResetEvent类:https://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent.aspx
AutoResetEvent类:https://msdn.microsoft.com/zh-cn/library/system.threading.autoresetevent.aspx
言归正传,在本例中,直到手动重置事件信号出现,mre.WaitOne()会一直等待。 mre.Set()将标记重置事件信号。 ManualResetEvent将释放当前正在等待的所有线程。AutoResetEvent将只释放一个等待的线程,并立即变为无信号。WaitOne()也可以接受超时作为参数。 如果Set()在超时期间未被调用,则线程被释放并且WaitOne()返回False。
以下是此功能的实现代码:
执行结果:
Starting with timeout of 5 seconds
Operation started
Starting with timeout of 5 but cancelling after 2 seconds
Finished...
我个人非常倾向于这个解决方案,它比我们使用Timer的解决方案更干净简洁。
对于我们提出的简单功能,ManualResetEvent和Timer解决方案都可以正常工作。 现在让我们增加点挑战性。
新的改进需求
假设我们现在可以连续多次调用StartWithTimeout(),而不是等待第一个超时完成后调用。
但是这里的预期行为是什么?实际上存在以下几种可能性:
在以前的StartWithTimeout超时期间调用StartWithTimeout时:忽略第二次启动。
在以前的StartWithTimeout超时期间调用StartWithTimeout时:停止初始话Start并使用新的StartWithTimeout。
在以前的StartWithTimeout超时期间调用StartWithTimeout时:在两个启动中调用DoOperation。 在StopOperationIfNotStartedYet中停止所有尚未开始的操作(在超时时间内)。
在以前的StartWithTimeout超时期间调用StartWithTimeout时:在两个启动中调用DoOperation。 在StopOperationIfNotStartedYet停止一个尚未开始的随机操作。
可能性1可以通过Timer和ManualResetEvent可以轻松实现。 事实上,我们已经在我们的Timer解决方案中涉及到了这个。
public void StartWithTimeout(int timeoutMillis) { if (_timer != null) return; ... public void StartWithTimeout(int timeoutMillis) { if (_timer != null) return; ... }可能性2也可以很容易地实现。 这个地方请允许我卖个萌,代码自己写哈^_^
可能性3不可能通过使用Timer来实现。 我们将需要有一个定时器的集合。 一旦停止操作,我们需要检查并处理定时器集合中的所有子项。 这种方法是可行的,但通过ManualResetEvent我们可以非常简洁和轻松的实现这一点!
可能性4跟可能性3相似,可以通过定时器的集合来实现。
可能性3:使用单个ManualResetEvent停止所有操作
让我们了解一下这里面遇到的难点:
假设我们调用StartWithTimeout 10秒超时。
1秒后,我们再次调用另一个StartWithTimeout,超时时间为10秒。
再过1秒后,我们再次调用另一个StartWithTimeout,超时时间为10秒。
预期的行为是这3个操作会依次10秒、11秒和12秒后启动。
如果5秒后我们会调用Stop(),那么预期的行为就是所有正在等待的操作都会停止, 后续的操作也无法进行。
我稍微改变下Program.cs,以便能够测试这个操作过程。 这是新的代码:
class Program { static void Main(string[] args) { var op = new MyOperation(); var handler = new OperationHandler(op); Console.WriteLine("Starting with timeout of 10 seconds, 3 times"); handler.StartWithTimeout(10 * 1000); Thread.Sleep(1000); handler.StartWithTimeout(10 * 1000); Thread.Sleep(1000); handler.StartWithTimeout(10 * 1000); Thread.Sleep(13 * 1000); Console.WriteLine("Starting with timeout of 10 seconds 3 times, but cancelling after 5 seconds"); handler.StartWithTimeout(10 * 1000); Thread.Sleep(1000); handler.StartWithTimeout(10 * 1000); Thread.Sleep(1000); handler.StartWithTimeout(10 * 1000); Thread.Sleep(5 * 1000); handler.StopOperationIfNotStartedYet(); Thread.Sleep(8 * 1000); Console.WriteLine("Finished..."); Console.ReadLine(); } }