Czy jest lepszy wzór oczekiwania na c#?

Znalazłem się w kodowaniu tego typu rzeczy kilka razy.

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

To po prostu wygląda jak zły kod, czy jest lepszy sposób na zrobienie tego / czy jest to objaw złego projektu?

Author: SwDevMan81, 2011-08-09

9 answers

Znacznie lepszym sposobem implementacji tego wzorca jest wystawienie obiektu Thing na zdarzenie, na które konsument może czekać. Na przykład a ManualResetEvent lub AutoResetEvent. To znacznie upraszcza Twój kod konsumenta, aby był następujący

if (!Thing.ManualResetEvent.WaitOne(sleep_time)) {
  throw new ItDidntHappen();
}

// It happened

Kod na stronie Thing również nie jest tak naprawdę bardziej złożony.

public sealed class Thing {
  public readonly ManualResetEvent ManualResetEvent = new ManualResetEvent(false);

  private void TheAction() {
    ...
    // Done.  Signal the listeners
    ManualResetEvent.Set();
  }
}
 98
Author: JaredPar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 14:26:10

Użyj zdarzeń.

Miej to, na co czekasz, aby wywołać zdarzenie po jego zakończeniu (lub nie udało się go zakończyć w wyznaczonym czasie), a następnie obsłużyć Zdarzenie w głównej aplikacji.

W ten sposób nie masz żadnych Sleep pętli.

 29
Author: ChrisF,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 13:59:04

Pętla nie jest strasznym sposobem czekania na coś, jeśli program nie ma nic innego do zrobienia podczas oczekiwania (na przykład podczas łączenia się z DB). Widzę jednak pewne problemy z Twoim.

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

W skrócie, powyższy kod wygląda bardziej jak "pętla retry", która została zmodyfikowana, aby działać bardziej jak timeout. Oto jak skonstruowałbym pętlę timeout:

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();
 12
Author: KeithS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 15:09:56

Jeśli to możliwe, należy zawinąć przetwarzanie asynchroniczne w Task<T>. To zapewnia najlepsze ze wszystkich światów:

  • możesz odpowiedzieć na zakończenie w sposób podobny do zdarzenia, używając kontynuacji zadania.
  • możesz poczekać używając rękojeści oczekującej, ponieważ Task<T> implementuje IAsyncResult.
  • zadania Można łatwo komponować za pomocą Async CTP; dobrze też grają z Rx.
  • zadania mają bardzo czystą wbudowaną obsługę wyjątków system (w szczególności prawidłowo zachowują ślad stosu).

Jeśli chcesz użyć timeoutu, RX lub asynchroniczny CTP może to zapewnić.

 9
Author: Stephen Cleary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 14:19:56

Rzuciłbym okiem na klasę WaitHandle. W szczególności klasa ManualResetEvent , która czeka aż obiekt zostanie ustawiony. Możesz również określić dla niego wartości limitu czasu i sprawdzić, czy został on ustawiony później.

// 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();
}
 4
Author: SwDevMan81,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 14:05:33

Wywołanie Thread.Sleep zawsze jest aktywnym czekaniem, którego należy unikać.
Alternatywą byłoby użycie timera. Dla łatwiejszego użycia, możesz zamknąć to w klasie.

 2
Author: Daniel Hilgarth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 13:59:35

Zazwyczaj odradzam rzucanie WYJĄTKÓW.

// 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
 2
Author: Ishpeck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 13:59:42

Myślę, że powinieneś użyć AutoResetEvents. Działają świetnie, gdy czekasz na kolejny wątek, aby zakończyć zadanie

Przykład:

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

 }
}
 2
Author: Kornél Regius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-09 14:10:09

Oto jak możesz to zrobić z System.Threading.Tasks:

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

Ale jeśli z jakiegoś powodu nie możesz używać zadań (jak wymóg. Net 2.0), możesz użyć ManualResetEvent, Jak wspomniano w odpowiedzi Jaredpara lub użyć czegoś takiego:

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

Przy podejściu Wait / Pulse nie tworzysz jawnie zdarzeń, więc nie musisz przejmować się ich usuwaniem.

Przykład użycia:

var rh = new RunHelper(
    () =>
    {
        Thread.Sleep(1000);
    });
if (rh.Wait(500))
{
    Console.WriteLine("Success.");
}
else
{
    Console.WriteLine("Timeout.");
}
 2
Author: Gebb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-23 17:02:12