有没有更好的等待模式的C#?
我发现自己编码这种types的东西几次。
for (int i = 0; i < 10; i++) { if (Thing.WaitingFor()) { break; } Thread.Sleep(sleep_time); } if(!Thing.WaitingFor()) { throw new ItDidntHappenException(); }
它看起来像不好的代码,有没有更好的方式做这/是一个糟糕的devise的症状?
实现这种模式的更好方法是让你的Thing
对象公开一个消费者可以等待的事件。 例如ManualResetEvent
或AutoResetEvent
。 这极大地简化了您的消费者代码,如下所示
if (!Thing.ManualResetEvent.WaitOne(sleep_time)) { throw new ItDidntHappen(); } // It happened
Thing
方面的代码也不是那么复杂。
public sealed class Thing { public readonly ManualResetEvent ManualResetEvent = new ManualResetEvent(false); private void TheAction() { ... // Done. Signal the listeners ManualResetEvent.Set(); } }
使用事件。
在完成(或未能在指定时间内完成)时,等待事件提交事件,然后在主应用程序中处理该事件。
这样你就没有任何Sleep
循环。
一个循环不是一个可怕的方式来等待一些东西,如果你的程序在等待的时候没有其他的东西(比如连接到一个数据库)。 不过,我看到你的一些问题。
//It's not apparent why you wait exactly 10 times for this thing to happen for (int i = 0; i < 10; i++) { //A method, to me, indicates significant code behind the scenes. //Could this be a property instead, or maybe a shared reference? if (Thing.WaitingFor()) { break; } //Sleeping wastes time; the operation could finish halfway through your sleep. //Unless you need the program to pause for exactly a certain time, consider //Thread.Yield(). //Also, adjusting the timeout requires considering how many times you'll loop. Thread.Sleep(sleep_time); } if(!Thing.WaitingFor()) { throw new ItDidntHappenException(); }
简而言之,上面的代码看起来更像是一个“重试循环”,这被称为超时工作。 以下是我将如何构造一个超时循环:
var complete = false; var startTime = DateTime.Now; var timeout = new TimeSpan(0,0,30); //a thirty-second timeout. //We'll loop as many times as we have to; how we exit this loop is dependent only //on whether it finished within 30 seconds or not. while(!complete && DateTime.Now < startTime.Add(timeout)) { //A property indicating status; properties should be simpler in function than methods. //this one could even be a field. if(Thing.WereWaitingOnIsComplete) { complete = true; break; } //Signals the OS to suspend this thread and run any others that require CPU time. //the OS controls when we return, which will likely be far sooner than your Sleep(). Thread.Yield(); } //Reduce dependence on Thing using our local. if(!complete) throw new TimeoutException();
如果可能的话,将asynchronous处理包装在Task<T>
。 这提供了最好的世界:
- 您可以使用任务继续以类似事件的方式响应完成。
- 您可以使用完成的可等待的句柄来等待,因为
Task<T>
实现了IAsyncResult
。 - 使用
Async CTP
可以轻松组合任务; 他们也和Rx
打得不错。 - 任务有一个非常干净的内置exception处理系统(尤其是,他们正确地保留了堆栈跟踪)。
如果您需要使用超时,那么Rx或asynchronousCTP可以提供。
我会看看WaitHandle类。 特别是等待对象设置的ManualResetEvent类。 您也可以为它指定超时值并检查是否在之后设置。
// Member variable ManualResetEvent manual = new ManualResetEvent(false); // Not set // Where you want to wait. manual.WaitOne(); // Wait for manual.Set() to be called to continue here if(!manual.WaitOne(0)) // Check if set { throw new ItDidntHappenException(); }
一个调用Thread.Sleep
总是一个积极的等待,应该避免。
另一种方法是使用计时器。 为了方便使用,可以将其封装到一个类中。
我通常不鼓励抛出exception。
// Inside a method... checks=0; while(!Thing.WaitingFor() && ++checks<10) { Thread.Sleep(sleep_time); } return checks<10; //False = We didn't find it, true = we did
我认为你应该使用AutoResetEvents。 当你等待另一个线程来完成它的任务时,它们工作的很好
例:
AutoResetEvent hasItem; AutoResetEvent doneWithItem; int jobitem; public void ThreadOne() { int i; while(true) { //SomeLongJob i++; jobitem = i; hasItem.Set(); doneWithItem.WaitOne(); } } public void ThreadTwo() { while(true) { hasItem.WaitOne(); ProcessItem(jobitem); doneWithItem.Set(); } }
这里是你如何使用System.Threading.Tasks
来做到这一点:
Task t = Task.Factory.StartNew( () => { Thread.Sleep(1000); }); if (t.Wait(500)) { Console.WriteLine("Success."); } else { Console.WriteLine("Timeout."); }
但是,如果由于某种原因(如.Net 2.0的要求)而无法使用任务,那么您可以使用JaredPar的答案中提到的ManualResetEvent
,或者使用如下所示的内容:
public class RunHelper { private readonly object _gate = new object(); private bool _finished; public RunHelper(Action action) { ThreadPool.QueueUserWorkItem( s => { action(); lock (_gate) { _finished = true; Monitor.Pulse(_gate); } }); } public bool Wait(int milliseconds) { lock (_gate) { if (_finished) { return true; } return Monitor.Wait(_gate, milliseconds); } } }
使用Wait / Pulse方法,您不需要明确地创build事件,因此您无需关心如何处置它们。
用法示例:
var rh = new RunHelper( () => { Thread.Sleep(1000); }); if (rh.Wait(500)) { Console.WriteLine("Success."); } else { Console.WriteLine("Timeout."); }