Me he encontrado codificando este tipo de cosas varias veces.
for (int i = 0; i < 10; i++)
{
if (Thing.WaitingFor())
{
break;
}
Thread.Sleep(sleep_time);
}
if(!Thing.WaitingFor())
{
throw new ItDidntHappenException();
}
Parece un código incorrecto, ¿hay una mejor manera de hacer esto / es un síntoma de un mal diseño?
c#
multithreading
design-patterns
probablemente en la playa
fuente
fuente
Utilice eventos.
Haga que lo que está esperando genere un evento cuando haya terminado (o no haya terminado dentro del tiempo asignado) y luego maneje el evento en su aplicación principal.
De esa forma no tendrás ningún
Sleep
bucle.fuente
Un bucle no es una forma TERRIBLE de esperar algo, si no hay nada más que pueda hacer su programa mientras espera (por ejemplo, mientras se conecta a una base de datos). Sin embargo, veo algunos problemas con el tuyo.
//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 resumen, el código anterior se parece más a un "bucle de reintento", que ha sido bastardado para funcionar más como un tiempo de espera. Así es como estructuraría un ciclo de tiempo de espera:
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();
fuente
Si es posible, envuelva el procesamiento asincrónico en un archivo
Task<T>
. Esto proporciona lo mejor de todos los mundos:Task<T>
implementsIAsyncResult
.Async CTP
; también juegan bien conRx
.Si necesita utilizar un tiempo de espera, Rx o Async CTP pueden proporcionarlo.
fuente
Echaría un vistazo a la clase WaitHandle . Específicamente la clase ManualResetEvent que espera hasta que se establece el objeto. También puede especificar valores de tiempo de espera para él y verificar si se configuró posteriormente.
// 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(); }
fuente
Una llamada a
Thread.Sleep
siempre es una espera activa que debe evitarse.Una alternativa sería utilizar un temporizador. Para un uso más fácil, podría encapsularlo en una clase.
fuente
Por lo general, desaconsejo lanzar excepciones.
// 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
fuente
Creo que deberías usar AutoResetEvents. Funcionan muy bien cuando estás esperando que otro hilo termine su tarea
Ejemplo:
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(); } }
fuente
Así es como puede hacerlo con
System.Threading.Tasks
:Task t = Task.Factory.StartNew( () => { Thread.Sleep(1000); }); if (t.Wait(500)) { Console.WriteLine("Success."); } else { Console.WriteLine("Timeout."); }
Pero si no puede usar Tasks por algún motivo (como un requisito de .Net 2.0), puede usarlo
ManualResetEvent
como se menciona en la respuesta de JaredPar o usar algo como esto: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); } } }
Con el enfoque Wait / Pulse no crea eventos explícitamente, por lo que no necesita preocuparse por eliminarlos.
Ejemplo de uso:
var rh = new RunHelper( () => { Thread.Sleep(1000); }); if (rh.Wait(500)) { Console.WriteLine("Success."); } else { Console.WriteLine("Timeout."); }
fuente