Y a-t-il un meilleur modèle d’attente pour c #?

Je me suis retrouvé à coder ce genre de chose à quelques resockets.

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

Cela ressemble à un mauvais code, y a-t-il un meilleur moyen de le faire / est-ce un symptôme de mauvaise conception?

Une méthode bien meilleure pour implémenter ce modèle consiste à exposer votre object Thing un événement sur lequel le consommateur peut attendre. Par exemple, un ManualResetEvent ou un AutoResetEvent . Cela simplifie grandement votre code de consommation pour être le suivant

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

Le code du côté de Thing n’est pas vraiment plus complexe.

 public sealed class Thing { public readonly ManualResetEvent ManualResetEvent = new ManualResetEvent(false); private void TheAction() { ... // Done. Signal the listeners ManualResetEvent.Set(); } } 

Utiliser des événements

Ayez ce que vous attendez pour déclencher un événement une fois qu’il est terminé (ou ne pas avoir terminé dans le délai imparti), puis gérez l’événement dans votre application principale.

De cette façon, vous n’avez pas de boucles de Sleep .

Une boucle n’est pas un moyen TERRIBLE d’attendre quelque chose, s’il n’y a rien d’autre à faire pendant que votre programme attend (par exemple lors de la connexion à un DB). Cependant, je vois des problèmes avec les vôtres.

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

En bref, le code ci-dessus ressemble plus à une "boucle de nouvelle tentative", qui a été bâtie pour fonctionner plus comme un délai d'attente. Voici comment je structurerais une boucle de 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(); 

Si possible, placez le traitement asynchrone dans une Task . Cela fournit le meilleur des mondes:

  • Vous pouvez répondre à l’achèvement de manière événementielle en utilisant des continuations de tâches .
  • Vous pouvez attendre en utilisant le handle d’attente d’achèvement, car la Task implémente IAsyncResult .
  • Les tâches sont facilement composables en utilisant le Async CTP ; ils jouent aussi bien avec Rx .
  • Les tâches ont un système de gestion des exceptions intégré très propre (en particulier, elles conservent correctement la trace de la stack).

Si vous devez utiliser un délai d’attente, Rx ou le CTP Async peuvent vous le fournir.

Je regarderais la classe WaitHandle . Spécifiquement la classe ManualResetEvent qui attend que l’object soit défini. Vous pouvez également spécifier des valeurs de délai d’attente et vérifier si elles ont été définies ultérieurement.

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

Un appel à Thread.Sleep est toujours une attente active qui devrait être évitée.
Une alternative serait d’utiliser une timer. Pour une utilisation plus facile, vous pouvez encapsuler cela dans une classe.

Je décourage généralement de lancer des exceptions.

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

Je pense que vous devriez utiliser AutoResetEvents. Ils fonctionnent bien lorsque vous attendez qu’un autre thread termine sa tâche

Exemple:

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

Voici comment vous pouvez le faire avec System.Threading.Tasks :

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

Mais si vous ne pouvez pas utiliser les tâches pour une raison quelconque (comme une exigence de .Net 2.0), vous pouvez utiliser ManualResetEvent comme indiqué dans la réponse de JaredPar ou utiliser quelque chose comme ceci:

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

Avec l’approche Wait / Pulse, vous ne créez pas explicitement d’événements pour ne pas avoir à vous en soucier.

Exemple d’utilisation:

 var rh = new RunHelper( () => { Thread.Sleep(1000); }); if (rh.Wait(500)) { Console.WriteLine("Success."); } else { Console.WriteLine("Timeout."); }