Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
c#有更好的等待模式吗?_C#_Multithreading_Design Patterns - Fatal编程技术网

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慢