velocidades - ¿Hay un mejor patrón de espera para c#?
velocidades patrones de espera oaci (9)
Me he encontrado codificando este tipo de cosas un par de veces.
for (int i = 0; i < 10; i++)
{
if (Thing.WaitingFor())
{
break;
}
Thread.Sleep(sleep_time);
}
if(!Thing.WaitingFor())
{
throw new ItDidntHappenException();
}
Simplemente parece un código incorrecto, ¿hay una mejor manera de hacerlo / es un síntoma de un mal diseño?
Aquí es cómo puedes 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 Tareas por algún motivo (como un requisito de .Net 2.0), puede usar el 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 Esperar / Pulsar, 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.");
}
Creo que deberías usar AutoResetEvents. Funcionan muy bien cuando esperas 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();
}
}
Por lo general, desaconsejo tirar 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
Si es posible, haga que el procesamiento asíncrono se ajuste a una Task<T>
. Esto proporciona lo mejor de todos los mundos:
- Puede responder a la finalización de una manera similar a un evento mediante el uso de continuación de tareas .
- Puede esperar utilizando el identificador waitable de la finalización porque
Task<T>
implementaIAsyncResult
. - Las tareas se pueden componer fácilmente utilizando
Async CTP
; también juegan bien conRx
. - Las tareas tienen un sistema de manejo de excepciones integrado muy limpio (en particular, conservan correctamente el seguimiento de la pila).
Si necesita utilizar un tiempo de espera, Rx o Async CTP pueden proporcionar eso.
Un bucle no es una manera TERRIBLE de esperar algo, si su programa no tiene nada más que hacer mientras espera (por ejemplo, mientras se conecta a un DB). Sin embargo, veo algunos problemas con los suyos.
//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 "ciclo de reintento", que ha sido bastardeado 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();
Una forma mucho mejor de implementar este patrón es hacer que su objeto Thing
exponga un evento en el que el consumidor puede esperar. Por ejemplo, un ManualResetEvent
o AutoResetEvent
. Esto simplifica enormemente su código de consumidor para ser el siguiente
if (!Thing.ManualResetEvent.WaitOne(sleep_time)) {
throw new ItDidntHappen();
}
// It happened
El código en el lado de la Thing
tampoco es realmente más complejo.
public sealed class Thing {
public readonly ManualResetEvent ManualResetEvent = new ManualResetEvent(false);
private void TheAction() {
...
// Done. Signal the listeners
ManualResetEvent.Set();
}
}
Una llamada a Thread.Sleep
siempre es una espera activa que debe evitarse.
Una alternativa sería usar un temporizador. Para un uso más fácil, puede encapsular eso en una clase.
Usa eventos
Haga que lo que está esperando genere un evento cuando finalice (o no termine dentro del tiempo asignado) y luego maneje el evento en su aplicación principal.
De esa forma no tienes ningún bucle de Suspensión.
WaitHandle 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 estableció después.
// 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();
}