c# - pause - Llamadas de función retrasadas
timer sleep c# (12)
Además de estar de acuerdo con las observaciones de diseño de los comentadores anteriores, ninguna de las soluciones fue lo suficientemente limpia para mí. .Net 4 proporciona clases Dispatcher
y Task
que hacen que retrasar la ejecución en el hilo actual sea bastante simple:
static class AsyncUtils
{
static public void DelayCall(int msec, Action fn)
{
// Grab the dispatcher from the current executing thread
Dispatcher d = Dispatcher.CurrentDispatcher;
// Tasks execute in a thread pool thread
new Task (() => {
System.Threading.Thread.Sleep (msec); // delay
// use the dispatcher to asynchronously invoke the action
// back on the original thread
d.BeginInvoke (fn);
}).Start ();
}
}
Para el contexto, estoy usando esto para eliminar el reblandecimiento de un ICommand
vinculado a un botón izquierdo del mouse en un elemento de la interfaz de usuario. Los usuarios hacen doble clic, lo que causaba todo tipo de estragos. (Sé que también podría usar los manejadores Click
/ DoubleClick
, pero quería una solución que funcionara con ICommand
s en todos los ámbitos).
public void Execute(object parameter)
{
if (!IsDebouncing) {
IsDebouncing = true;
AsyncUtils.DelayCall (DebouncePeriodMsec, () => {
IsDebouncing = false;
});
_execute ();
}
}
¿Existe un buen método simple para retrasar una llamada de función mientras se deja que el hilo continúe ejecutándose?
p.ej
public void foo()
{
// Do stuff!
// Delayed call to bar() after x number of ms
// Do more Stuff
}
public void bar()
{
// Only execute once foo has finished
}
Soy consciente de que esto se puede lograr utilizando un temporizador y controladores de eventos, pero me preguntaba si existe una forma estándar de c # para lograrlo.
Si alguien tiene curiosidad, la razón por la que esto es obligatorio es que foo () y bar () están en clases diferentes (singleton) que necesitan llamarse entre sí en circunstancias excepcionales. El problema es que esto se hace en la inicialización así que foo necesita llamar a la barra que necesita una instancia de la clase foo que se está creando ... de ahí la llamada diferida a bar () para asegurarse de que foo está completamente instanciado. Leyendo esto de nuevo casi huele a mal diseño!
EDITAR
Tomaré los puntos sobre mal diseño bajo consideración! Durante mucho tiempo pensé que podría mejorar el sistema, sin embargo, esta desagradable situación solo ocurre cuando se lanza una excepción, en cualquier otro momento los dos singleton coexisten muy bien. Creo que no voy a desordenar con desagradables patrones de sincronización, sino que voy a refactorizar la inicialización de una de las clases.
Bueno, tendría que estar de acuerdo con el punto de "diseño" ... pero probablemente puedas usar un monitor para avisarle a alguien cuando el otro haya pasado la sección crítica ...
public void foo() {
// Do stuff!
object syncLock = new object();
lock (syncLock) {
// Delayed call to bar() after x number of ms
ThreadPool.QueueUserWorkItem(delegate {
lock(syncLock) {
bar();
}
});
// Do more Stuff
}
// lock now released, bar can begin
}
De hecho, es un diseño muy malo, y mucho menos el singleton en sí mismo es un mal diseño.
Sin embargo, si realmente necesita retrasar la ejecución, esto es lo que puede hacer:
BackgroundWorker barInvoker = new BackgroundWorker();
barInvoker.DoWork += delegate
{
Thread.Sleep(TimeSpan.FromSeconds(1));
bar();
};
barInvoker.RunWorkerAsync();
Sin embargo, esto invocará a bar()
en un hilo separado. Si necesita llamar a bar()
en el hilo original, puede que necesite mover la invocación de bar()
al manejador RunWorkerCompleted
o hacer un poco de hacking con SynchronizationContext
.
Esto funcionará en versiones anteriores de .NET
Contras: se ejecutará en su propio hilo
class CancelableDelay
{
Thread delayTh;
Action action;
int ms;
public static CancelableDelay StartAfter(int milliseconds, Action action)
{
CancelableDelay result = new CancelableDelay() { ms = milliseconds };
result.action = action;
result.delayTh = new Thread(result.Delay);
result.delayTh.Start();
return result;
}
private CancelableDelay() { }
void Delay()
{
try
{
Thread.Sleep(ms);
action.Invoke();
}
catch (ThreadAbortException)
{ }
}
public void Cancel() => delayTh.Abort();
}
Uso:
var job = CancelableDelay.StartAfter(1000, () => { WorkAfter1sec(); });
job.Cancel(); //to cancel the delayed job
Gracias a la moderna C # 5/6 :)
public void foo()
{
Task.Delay(1000).ContinueWith(t=> bar());
}
public void bar()
{
// do stuff
}
He estado buscando algo como esto yo mismo: se me ocurrió lo siguiente, aunque usa un temporizador, lo usa solo una vez para el retraso inicial y no requiere ninguna llamada de Sleep
...
public void foo()
{
System.Threading.Timer timer = null;
timer = new System.Threading.Timer((obj) =>
{
bar();
timer.Dispose();
},
null, 1000, System.Threading.Timeout.Infinite);
}
public void bar()
{
// do stuff
}
(gracias a Fred Deschenes por la idea de disponer el temporizador dentro de la devolución de llamada)
No hay una forma estándar de retrasar una llamada a una función que no sea usar un temporizador y eventos.
Esto suena como el patrón anti GUI de retrasar una llamada a un método para que pueda estar seguro de que el formulario ha terminado de diseñar. No es Buena idea.
Parece que el control de la creación de estos dos objetos y su interdependencia debe controlarse externamente, en lugar de entre las clases mismas.
Pensé que la solución perfecta sería tener un temporizador para manejar la acción retardada. A FxCop no le gusta cuando tienes un intervalo de menos de un segundo. Necesito retrasar mis acciones hasta DESPUÉS de que mi DataGrid haya completado la ordenación por columna. Pensé que un temporizador de una sola toma (AutoReset = false) sería la solución, y funciona perfectamente. Y, FxCop no me permitirá suprimir la advertencia!
Sobre la base de la respuesta de David O''Donoghue, aquí hay una versión optimizada del delegado diferido:
using System.Windows.Forms;
using System.Collections.Generic;
using System;
namespace MyTool
{
public class DelayedDelegate
{
static private DelayedDelegate _instance = null;
private Timer _runDelegates = null;
private Dictionary<MethodInvoker, DateTime> _delayedDelegates = new Dictionary<MethodInvoker, DateTime>();
public DelayedDelegate()
{
}
static private DelayedDelegate Instance
{
get
{
if (_instance == null)
{
_instance = new DelayedDelegate();
}
return _instance;
}
}
public static void Add(MethodInvoker pMethod, int pDelay)
{
Instance.AddNewDelegate(pMethod, pDelay * 1000);
}
public static void AddMilliseconds(MethodInvoker pMethod, int pDelay)
{
Instance.AddNewDelegate(pMethod, pDelay);
}
private void AddNewDelegate(MethodInvoker pMethod, int pDelay)
{
if (_runDelegates == null)
{
_runDelegates = new Timer();
_runDelegates.Tick += RunDelegates;
}
else
{
_runDelegates.Stop();
}
_delayedDelegates.Add(pMethod, DateTime.Now + TimeSpan.FromMilliseconds(pDelay));
StartTimer();
}
private void StartTimer()
{
if (_delayedDelegates.Count > 0)
{
int delay = FindSoonestDelay();
if (delay == 0)
{
RunDelegates();
}
else
{
_runDelegates.Interval = delay;
_runDelegates.Start();
}
}
}
private int FindSoonestDelay()
{
int soonest = int.MaxValue;
TimeSpan remaining;
foreach (MethodInvoker invoker in _delayedDelegates.Keys)
{
remaining = _delayedDelegates[invoker] - DateTime.Now;
soonest = Math.Max(0, Math.Min(soonest, (int)remaining.TotalMilliseconds));
}
return soonest;
}
private void RunDelegates(object pSender = null, EventArgs pE = null)
{
try
{
_runDelegates.Stop();
List<MethodInvoker> removeDelegates = new List<MethodInvoker>();
foreach (MethodInvoker method in _delayedDelegates.Keys)
{
if (DateTime.Now >= _delayedDelegates[method])
{
method();
removeDelegates.Add(method);
}
}
foreach (MethodInvoker method in removeDelegates)
{
_delayedDelegates.Remove(method);
}
}
catch (Exception ex)
{
}
finally
{
StartTimer();
}
}
}
}
La clase podría mejorarse un poco más utilizando una clave única para los delegados. Porque si agrega el mismo delegado por segunda vez antes de disparar el primero, es posible que tenga un problema con el diccionario.
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));
}
public static class DelayedDelegate
{
static Timer runDelegates;
static Dictionary<MethodInvoker, DateTime> delayedDelegates = new Dictionary<MethodInvoker, DateTime>();
static DelayedDelegate()
{
runDelegates = new Timer();
runDelegates.Interval = 250;
runDelegates.Tick += RunDelegates;
runDelegates.Enabled = true;
}
public static void Add(MethodInvoker method, int delay)
{
delayedDelegates.Add(method, DateTime.Now + TimeSpan.FromSeconds(delay));
}
static void RunDelegates(object sender, EventArgs e)
{
List<MethodInvoker> removeDelegates = new List<MethodInvoker>();
foreach (MethodInvoker method in delayedDelegates.Keys)
{
if (DateTime.Now >= delayedDelegates[method])
{
method();
removeDelegates.Add(method);
}
}
foreach (MethodInvoker method in removeDelegates)
{
delayedDelegates.Remove(method);
}
}
}
Uso:
DelayedDelegate.Add(MyMethod,5);
void MyMethod()
{
MessageBox.Show("5 Seconds Later!");
}