¿Existe un método simple y agradable para retrasar una llamada a una 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 mediante el uso de 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 necesario es que foo () y bar () están en clases diferentes (singleton) que necesito llamar entre sí en circunstancias excepcionales. El problema es que esto se hace en la inicialización, por lo que foo necesita llamar a bar, que necesita una instancia de la clase foo que se está creando ... de ahí la llamada retardada a bar () para garantizar que foo esté completamente instanciado. ¡Casi huele a mal diseño!
EDITAR
¡Tomaré en cuenta los puntos sobre el mal diseño! 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 todas las demás ocasiones los dos singleton coexisten muy bien. Creo que no voy a dar vueltas con patrones asincrónicos desagradables, sino que voy a refactorizar la inicialización de una de las clases.
Awake
yStart
fases. En laAwake
fase, se configura usted mismo, y al final de esta fase se inicializan todos los objetos. Durante laStart
fase, los objetos pueden comenzar a comunicarse entre sí.Respuestas:
Gracias al moderno C # 5/6 :)
public void foo() { Task.Delay(1000).ContinueWith(t=> bar()); } public void bar() { // do stuff }
fuente
Yo mismo he estado buscando algo como esto: se me ocurrió lo siguiente, aunque usa un temporizador, solo lo usa una vez para el retraso inicial y no requiere ninguna
Sleep
llamada ...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 deshacerse del temporizador dentro de la devolución de llamada)
fuente
timer
variable local desde dentro de la lambda que se vincula al objeto delegadocb
hace que se coloque en una tienda anónima (detalle de implementación de cierre) que hará que elTimer
objeto sea accesible desde la perspectiva del GC mientras elTimerCallback
delegado en sí sea accesible . En otras palabras,Timer
se garantiza que el objeto no se recolectará como basura hasta después de que el grupo de subprocesos invoque el objeto delegado.Aparte de estar de acuerdo con las observaciones de diseño de los comentaristas anteriores, ninguna de las soluciones fue lo suficientemente limpia para mí. .Net 4 proporciona
Dispatcher
yTask
clases 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 rebote y estar
ICommand
vinculado al botón izquierdo del mouse hacia arriba en un elemento de la interfaz de usuario. Los usuarios estaban haciendo doble clic, lo que estaba causando todo tipo de estragos. (Sé que también podría usarClick
/DoubleClick
handlers, pero quería una solución que funcionara conICommand
s en todos los ámbitos).public void Execute(object parameter) { if (!IsDebouncing) { IsDebouncing = true; AsyncUtils.DelayCall (DebouncePeriodMsec, () => { IsDebouncing = false; }); _execute (); } }
fuente
Parece que el control de la creación de ambos objetos y su interdependencia debe controlarse externamente, en lugar de entre las clases mismas.
fuente
De hecho, es un diseño muy malo, y mucho menos 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 se invocará
bar()
en un hilo separado. Si necesita llamarbar()
al hilo original, es posible que deba mover labar()
invocación alRunWorkerCompleted
controlador o hacer un poco de pirateo conSynchronizationContext
.fuente
Bueno, tendría que estar de acuerdo con el punto de "diseño" ... pero probablemente puedas usar un monitor para que uno sepa cuando el otro ha 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 }
fuente
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!"); }
fuente
Pensé que la solución perfecta sería tener un temporizador que manejara la acción retrasada. A FxCop no le gusta cuando tiene un intervalo de menos de un segundo. Necesito retrasar mis acciones hasta DESPUÉS de que mi DataGrid haya completado la clasificación por columna. Pensé que un temporizador de un disparo (AutoReset = false) sería la solución, y funciona perfectamente. ¡Y FxCop no me permitirá suprimir la advertencia!
fuente
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
fuente
No existe 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 se ha terminado de diseñar. No es Buena idea.
fuente
Sobre la base de la respuesta de David O'Donoghue, aquí hay una versión optimizada del Delegado retrasado:
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 que se active el primero, es posible que tenga un problema con el diccionario.
fuente
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)); }
fuente