有没有更好的等待模式的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对象公开一个消费者可以等待的事件。 例如ManualResetEventAutoResetEvent 。 这极大地简化了您的消费者代码,如下所示

 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."); }