interval - timer event c#
La mejor forma de crear una funciĆ³n demorada "ejecutar una vez" en C# (11)
Intento crear una función que adopte una acción y un tiempo de espera, y ejecuta la acción después del tiempo de espera. La función es no bloquear. La función debe ser segura para subprocesos. También realmente, realmente quiero evitar Thread.Sleep ().
Hasta ahora, lo mejor que puedo hacer es esto:
long currentKey = 0;
ConcurrentDictionary<long, Timer> timers = new ConcurrentDictionary<long, Timer>();
protected void Execute(Action action, int timeout_ms)
{
long currentKey = Interlocked.Increment(ref currentKey);
Timer t = new Timer(
(key) =>
{
action();
Timer lTimer;
if(timers.TryRemove((long)key, out lTimer))
{
lTimer.Dispose();
}
}, currentKey, Timeout.Infinite, Timeout.Infinite
);
timers[currentKey] = t;
t.Change(timeout_ms, Timeout.Infinite);
}
El problema es que llamar a Dispose () desde la devolución de llamada no puede ser bueno. No estoy seguro de si es seguro "caerse" del final, es decir, los temporizadores se consideran en vivo mientras se ejecutan sus lambdas, pero incluso si este es el caso, preferiría disponer de él correctamente.
El "fuego una vez con retraso" parece ser un problema tan común que debería haber una manera fácil de hacerlo, probablemente alguna otra biblioteca en System.Threading Me falta, pero en este momento la única solución que puedo pensar es la modificación de lo anterior con una tarea de limpieza dedicada ejecutándose en un intervalo. ¿Algún consejo?
¿Por qué no invocar simplemente su parámetro de acción en una acción asincrónica?
Action timeoutMethod = () =>
{
Thread.Sleep(timeout_ms);
action();
};
timeoutMethod.BeginInvoke();
El código de treze está funcionando bien. Esto podría ayudar a los que tienen que usar versiones anteriores de .NET:
private static volatile List<System.Threading.Timer> _timers = new List<System.Threading.Timer>();
private static object lockobj = new object();
public static void SetTimeout(Action action, int delayInMilliseconds)
{
System.Threading.Timer timer = null;
var cb = new System.Threading.TimerCallback((state) =>
{
lock (lockobj)
_timers.Remove(timer);
timer.Dispose();
action();
});
lock (lockobj)
_timers.Add(timer = new System.Threading.Timer(cb, null, delayInMilliseconds, System.Threading.Timeout.Infinite));
}
El modelo que tienes, usando un temporizador de una sola toma, es definitivamente el camino a seguir. Ciertamente no desea crear un nuevo hilo para cada uno de ellos. Puede tener un solo hilo y una cola de prioridad de acciones con tiempo, pero eso es una complejidad innecesaria.
Llamar a Dispose
en la devolución de llamada probablemente no sea una buena idea, aunque estaría tentado de intentarlo. Me parece recordar haber hecho esto en el pasado, y funcionó bien. Pero es algo poco convincente, lo admito.
Solo puede quitar el temporizador de la colección y no eliminarlo. Sin referencias al objeto, será elegible para la recolección de basura, lo que significa que el finalizador llamará al método Dispose
. Simplemente no tan oportuno como te gustaría. Pero no debería ser un problema. Solo estás filtrando una manija por un breve período. Siempre y cuando no tenga miles de estas cosas sentadas sin estar dispuestas durante un período prolongado, no va a ser un problema.
Otra opción es tener una cola de temporizadores que permanezca asignada, pero desactivada (es decir, su tiempo de espera e intervalos establecidos en Timeout.Infinite
de Timeout.Infinite
). Cuando necesite un temporizador, saque uno de la cola, configúrelo y agréguelo a su colección. Cuando expira el tiempo de espera, borra el temporizador y lo vuelve a poner en la cola. Puede hacer crecer la cola dinámicamente si es necesario, e incluso podría prepararla de vez en cuando.
Eso evitará que filtres un temporizador por cada evento. En cambio, tendrás un grupo de temporizadores (muy parecido al grupo de subprocesos, ¿no?).
Esto parece funcionar para mí. Me permite invocar _connection.Start () después de un retraso de 15 segundos. El parámetro de -1 milisegundo simplemente dice "no repetir".
// Instance or static holder that won''t get garbage collected (thanks chuu)
System.Threading.Timer t;
// Then when you need to delay something
var t = new System.Threading.Timer(o =>
{
_connection.Start();
},
null,
TimeSpan.FromSeconds(15),
TimeSpan.FromMilliseconds(-1));
Esto puede ser un poco tarde, pero aquí está la solución que estoy usando actualmente para manejar la ejecución retrasada:
public class OneShotTimer
{
private volatile readonly Action _callback;
private OneShotTimer(Action callback, long msTime)
{
_callback = callback;
var timer = new Threading.Timer(TimerProc);
timer.Change(msTime, Threading.Timeout.Infinite);
}
private void TimerProc(object state)
{
try {
// The state object is the Timer object.
((Threading.Timer)state).Dispose();
_callback.Invoke();
} catch (Exception ex) {
// Handle unhandled exceptions
}
}
public static OneShotTimer Start(Action callback, TimeSpan time)
{
return new OneShotTimer(callback, Convert.ToInt64(time.TotalMilliseconds));
}
public static OneShotTimer Start(Action callback, long msTime)
{
return new OneShotTimer(callback, msTime);
}
}
Puedes usarlo así:
OneShotTimer.Start(() => DoStuff(), TimeSpan.FromSeconds(1))
La documentación indica claramente que System.Timers.Timer ha AutoReset
propiedad AutoReset
solo para lo que está preguntando:
https://msdn.microsoft.com/en-us/library/system.timers.timer.autoreset(v=vs.110).aspx
Mi ejemplo:
void startTimerOnce()
{
Timer tmrOnce = new Timer();
tmrOnce.Tick += tmrOnce_Tick;
tmrOnce.Interval = 2000;
tmrOnce.Start();
}
void tmrOnce_Tick(object sender, EventArgs e)
{
//...
((Timer)sender).Dispose();
}
No hay nada incorporado en .Net 4 para hacer esto bien. Thread.Sleep o incluso AutoResetEvent.WaitOne (timeout) no son buenos - atarán los recursos del grupo de subprocesos, ¡me he quemado intentando esto!
La solución de peso más liviano es usar un temporizador, especialmente si tendrá que realizar muchas tareas.
Primero haga una clase de tarea programada simple:
class ScheduledTask
{
internal readonly Action Action;
internal System.Timers.Timer Timer;
internal EventHandler TaskComplete;
public ScheduledTask(Action action, int timeoutMs)
{
Action = action;
Timer = new System.Timers.Timer() { Interval = timeoutMs };
Timer.Elapsed += TimerElapsed;
}
private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Timer.Stop();
Timer.Elapsed -= TimerElapsed;
Timer = null;
Action();
TaskComplete(this, EventArgs.Empty);
}
}
Luego, crea una clase de programador, de nuevo, muy simple:
class Scheduler
{
private readonly ConcurrentDictionary<Action, ScheduledTask> _scheduledTasks = new ConcurrentDictionary<Action, ScheduledTask>();
public void Execute(Action action, int timeoutMs)
{
var task = new ScheduledTask(action, timeoutMs);
task.TaskComplete += RemoveTask;
_scheduledTasks.TryAdd(action, task);
task.Timer.Start();
}
private void RemoveTask(object sender, EventArgs e)
{
var task = (ScheduledTask) sender;
task.TaskComplete -= RemoveTask;
ScheduledTask deleted;
_scheduledTasks.TryRemove(task.Action, out deleted);
}
}
Se puede llamar de la siguiente manera, y es muy ligero:
var scheduler = new Scheduler();
scheduler.Execute(() => MessageBox.Show("hi1"), 1000);
scheduler.Execute(() => MessageBox.Show("hi2"), 2000);
scheduler.Execute(() => MessageBox.Show("hi3"), 3000);
scheduler.Execute(() => MessageBox.Show("hi4"), 4000);
No sé qué versión de C # estás usando. Pero creo que podrías lograr esto usando la biblioteca de tareas. Entonces se vería algo así.
public class PauseAndExecuter
{
public async Task Execute(Action action, int timeoutInMilliseconds)
{
await Task.Delay(timeoutInMilliseconds);
action();
}
}
Si no le importa demasiado la granularidad del tiempo, puede crear un temporizador que funcione cada segundo y verifique las acciones caducadas que deben ponerse en cola en el ThreadPool. Solo usa la clase de cronómetro para verificar el tiempo de espera.
Puede usar su enfoque actual, excepto que su Diccionario tendrá Cronómetro como su Clave y Acción como su Valor. Luego, simplemente itera en todos los KeyValuePairs y encuentra el cronómetro que expira, pone la acción en cola y luego la elimina. Sin embargo, obtendrás un mejor rendimiento y uso de la memoria de una LinkedList (ya que estarás enumerando todo el conjunto cada vez y será más fácil eliminar un elemento).
Uso este método para programar una tarea por un tiempo específico:
public void ScheduleExecute(Action action, DateTime ExecutionTime)
{
Task WaitTask = Task.Delay(ExecutionTime.Subtract(DateTime.Now).TotalMilliseconds);
WaitTask.ContinueWith(() => action());
WaitTask.Start();
}
Cabe señalar que esto solo funciona durante unos 24 días debido al valor máximo de int32.