我发现自己已经对这种类型的东西进行了几次编码。
for (int i = 0; i < 10; i++)
{
if (Thing.WaitingFor())
{
break;
}
Thread.Sleep(sleep_time);
}
if(!Thing.WaitingFor())
{
throw new ItDidntHappenException();
}
它看起来像是不良代码,是否有更好的方法可以做到?这是不良设计的征兆吗?
Answers:
实现此模式的一种更好的方法是让您的Thing
对象公开一个事件,供消费者等待。例如aManualResetEvent
或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();
我来看看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
始终调用是一种积极的等待,应该避免。
一种替代方法是使用计时器。为了更容易使用,您可以将其封装到一个类中。
我认为您应该使用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的要求)而无法使用Tasks,则可以ManualResetEvent
按照JaredPar的回答中所述使用,也可以使用类似以下的方法:
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);
}
}
}
使用“等待/脉冲”方法,您无需显式创建事件,因此无需关心如何处置它们。
用法示例:
var rh = new RunHelper(
() =>
{
Thread.Sleep(1000);
});
if (rh.Wait(500))
{
Console.WriteLine("Success.");
}
else
{
Console.WriteLine("Timeout.");
}