有没有更好的 C # 等待模式?

我发现自己已经编写过好几次这样的程序了。

for (int i = 0; i < 10; i++)
{
if (Thing.WaitingFor())
{
break;
}
Thread.Sleep(sleep_time);
}
if(!Thing.WaitingFor())
{
throw new ItDidntHappenException();
}

它看起来就像坏代码,有没有更好的方法来做这件事/这是坏设计的症状吗?

5911 次浏览

使用事件。

让您正在等待的事件在完成(或未能在分配的时间内完成)时引发,然后在主应用程序中处理该事件。

这样你就没有 Sleep循环了。

Thread.Sleep的调用总是一个应该避免的主动等待。
另一种方法是使用计时器,为了更容易使用,可以将它封装到一个类中。

我通常不鼓励抛出异常。

// 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

我想看看 等待处理课程。特别是等待对象设置的 手动重置事件类。您还可以为它指定超时值,并检查它是否是事后设置的。

// 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();
}

实现此模式的一个更好的方法是让 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();
}
}

我认为你应该使用 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();


}
}

如果可能的话,将异步处理封装在 Task<T>中。这提供了最好的处理方式:

  • 您可以使用 task continuations以类似事件的方式响应完成。
  • 您可以使用完成的等待句柄等待,因为 Task<T>实现了 IAsyncResult
  • 使用 Async CTP可以很容易地组合任务; 使用 Rx也可以很好地完成任务。
  • 任务有一个非常干净的内置异常处理系统(特别是,它们正确地保存了堆栈跟踪)。

如果您需要使用超时,那么 Rx 或异步 CTP 可以提供这种服务。

A loop is not a TERRIBLE way to wait for something, if there's nothing else for your program to do while it waits (for instance while connecting to a DB). However, I see some issues with yours.

    //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();

下面是如何使用 System.Threading.Tasks:

Task t = Task.Factory.StartNew(
() =>
{
Thread.Sleep(1000);
});
if (t.Wait(500))
{
Console.WriteLine("Success.");
}
else
{
Console.WriteLine("Timeout.");
}

But if you can't use Tasks for some reason (like a requirement of .Net 2.0) then you can use ManualResetEvent as mentioned in JaredPar's answer or use something like this:

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

使用等待/脉冲方法,您不需要显式创建 Events,因此不需要关心如何处置它们。

用法例子:

var rh = new RunHelper(
() =>
{
Thread.Sleep(1000);
});
if (rh.Wait(500))
{
Console.WriteLine("Success.");
}
else
{
Console.WriteLine("Timeout.");
}