c#有更好的等待模式吗?

时间:2011-08-09 13:55:36

标签: 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();
}

它只是看起来很糟糕的代码,有没有更好的方法来做这个/它是一个糟糕的设计的症状?

9 个答案:

答案 0 :(得分:98)

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

答案 1 :(得分:29)

使用活动。

当你完成一个事件(或者在规定的时间内未能完成)然后在主应用程序中处理事件时,你有等待事件。

这样你就没有任何Sleep循环。

答案 2 :(得分:12)

循环不是一种等待某事的可怕方式,如果你的程序在等待时没有别的东西(例如在连接到数据库时)。但是,我发现你的一些问题。

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

答案 3 :(得分:9)

如果可能,请将异步处理包装在Task<T>中。这提供了所有世界中最好的:

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

如果您需要使用超时,则Rx或Async CTP可以提供该超时。

答案 4 :(得分:4)

我会看一下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();
}

答案 5 :(得分:2)

Thread.Sleep的呼叫始终是一个应该避免的主动等待 一种替代方案是使用计时器。为了便于使用,您可以将其封装到类中。

答案 6 :(得分:2)

我通常不鼓励抛出异常。

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

答案 7 :(得分:2)

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

 }
}

答案 8 :(得分:2)

以下是使用System.Threading.Tasks

执行此操作的方法
Task t = Task.Factory.StartNew(
    () =>
    {
        Thread.Sleep(1000);
    });
if (t.Wait(500))
{
    Console.WriteLine("Success.");
}
else
{
    Console.WriteLine("Timeout.");
}

但是如果由于某种原因你不能使用Tasks(比如.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.");
}