c#有更好的等待模式吗?
我发现自己编写过几次这种类型的代码c#有更好的等待模式吗?,c#,multithreading,design-patterns,C#,Multithreading,Design Patterns,我发现自己编写过几次这种类型的代码 for (int i = 0; i < 10; i++) { if (Thing.WaitingFor()) { break; } Thread.Sleep(sleep_time); } if(!Thing.WaitingFor()) { throw new ItDidntHappenException(); } for(int i=0;i
for (int i = 0; i < 10; i++)
{
if (Thing.WaitingFor())
{
break;
}
Thread.Sleep(sleep_time);
}
if(!Thing.WaitingFor())
{
throw new ItDidntHappenException();
}
for(int i=0;i<10;i++)
{
if(Thing.WaitingFor())
{
打破
}
睡眠(睡眠时间);
}
如果(!Thing.WaitingFor())
{
抛出新的ITDidnthAppendException();
}
这看起来像是糟糕的代码,有没有更好的方法来做到这一点/这是糟糕设计的征兆 使用事件
让您正在等待的对象在事件完成(或未能在分配的时间内完成)时引发事件,然后在主应用程序中处理该事件
这样你就不会有任何
睡眠循环。对线程的调用。睡眠始终是一种活动等待,应该避免。
另一种选择是使用计时器。为了便于使用,您可以将其封装到类中。我通常不鼓励抛出异常
// 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
//方法内部。。。
检查=0;
虽然(!Thing.WaitingFor()&&&++检查但我会查看该类。特别是等待对象设置的类。您还可以为其指定超时值,并检查是否在之后设置
// 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();
}
实现此模式的更好方法是让对象公开消费者可以等待的事件。例如ManualResetEvent
或AutoResetEvent
。这大大简化了消费者代码
if (!Thing.ManualResetEvent.WaitOne(sleep_time)) {
throw new ItDidntHappen();
}
// It happened
方面的代码也并不复杂
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
实现了IAsyncResult
- 任务可以很容易地使用进行组合;它们也可以很好地使用
- 任务有一个非常干净的内置异常处理系统(特别是,它们正确地保留了堆栈跟踪)
如果您需要使用超时,那么Rx或异步CTP可以提供此功能。如果您的程序在等待时(例如,在连接到DB时)没有其他事情要做,则循环不是一种可怕的等待方式。但是,我发现您的循环存在一些问题
//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();
}
//不清楚为什么要等整整10次才发生这种事情
对于(int i=0;i<10;i++)
{
//对我来说,一个方法表示幕后的重要代码。
//这是一个属性,还是一个共享引用?
if(Thing.WaitingFor())
{
打破
}
//睡眠浪费时间;手术可能会在你睡眠的中途完成。
除非你需要程序暂停一段时间,考虑一下
//Thread.Yield()。
//此外,调整超时需要考虑循环次数。
睡眠(睡眠时间);
}
如果(!Thing.WaitingFor())
{
抛出新的ITDidnthAppendException();
}
简言之,上面的代码看起来更像是一个“重试循环”,它的工作更像是一个超时。下面是我将如何构造一个超时循环:
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();
var complete=false;
var startTime=DateTime.Now;
var timeout=new TimeSpan(0,0,30);//三十秒超时。
//我们将尽可能多地循环;如何退出这个循环只取决于
//是否在30秒内完成。
while(!complete&&DateTime.Now
以下是如何使用系统.线程化.任务来完成此任务的方法
:
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方法,您不需要显式创建事件,因此不需要关心如何处理它们
用法示例:
var rh = new RunHelper(
() =>
{
Thread.Sleep(1000);
});
if (rh.Wait(500))
{
Console.WriteLine("Success.");
}
else
{
Console.WriteLine("Timeout.");
}
+1谢谢Jared-这会处理异常情况,因为它不会很快发生。我认为您需要!使用if语句,否则它会在发生异常时抛出异常。您将在何时使用它们?(自动与手动)“自动”始终只允许一个等待线程通过,因为第一个线程被释放后,它就会被重置。“手动”允许所有等待线程通过,直到它被手动重置。@Vinko-添加到Tuskan的注释中,如果你想检查ResetEvent触发或超时后的状态,那么你必须使用ManualResetEvent,因为AutoResetEvent在从WaitOne调用返回后重置。因此OP的示例需要手动重置事件,JaredPar的示例不检查状态,如果AutoResetEvent+1,效果会更好谢谢Chris,您如何处理在特定时间内未发生的事件(在这些情况下我很关心)。在我的脑海中,我仍然在使用sleeps。@Richard-请参阅@lshpeck-这里不抛出异常有什么原因吗?如果姐妹服务正在运行,我希望会发生这种情况。实际代码是检查另一个服务是否正在执行某项工作。如果该服务未打开,它将失败,因此会抛出并捕获异常r堆栈的另一端.Thread.Yield很有趣,尽管DateTime.Now比DateTi慢