Llamadas de función retrasadas


¿Existe un método sencillo para retrasar una llamada a una función mientras se deja que el hilo continúe ejecutándose?

Por ejemplo

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 hay una forma estándar de c# para lograr esto.

Si alguien tiene curiosidad, la razón por la que esto se requiere es que foo() y bar() están en diferentes clases (singleton) a las que necesito llamar en circunstancias excepcionales. El problema 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 retrasada a bar () para asegurar que foo está completamente instanciado.. Leer esto de nuevo casi huele a mal diseño !

EDITAR

Voy a tomar los puntos sobre el mal diseño bajo consideración! Durante mucho tiempo he pensado que podría ser capaz de mejorar el sistema, sin embargo, esta situación desagradable solo se produce cuando una excepción es lanzado, en todas las otras veces los dos singletons coexisten muy bien. Creo que no voy a perder el tiempo con patrones asíncronos desagradables, sino que voy a refactorizar la inicialización de una de las clases.

Author: ROMANIA_engineer, 2009-02-13

12 answers

Gracias al moderno C # 5/6:)

public void foo()
{
    Task.Delay(1000).ContinueWith(t=> bar());
}

public void bar()
{
    // do stuff
}
 94
Author: Korayem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-27 14:17:22

Yo mismo he estado buscando algo como esto - se me ocurrió lo siguiente, aunque usa un temporizador, lo usa solo una vez para el retraso inicial, y no requiere llamadas 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 deshacerse del temporizador dentro de la devolución de llamada)

 88
Author: dodgy_coder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:40

Aparte de estar de acuerdo con las observaciones de diseño de los comentaristas anteriores, ninguna de las soluciones era lo suficientemente limpia para mí. .Net 4 proporciona Dispatcher y Task las clases que hacen retrasar la ejecución en el subproceso actual muy 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 rebotar un ICommand atado a un botón izquierdo del ratón en un elemento de la interfaz de usuario. Los usuarios están haciendo doble clic que estaba causando todo tipo de estragos. (Sé que también podría usar Click / DoubleClick manejadores, pero yo quería una solución que funciona con ICommands en todos los ámbitos).

public void Execute(object parameter)
{
    if (!IsDebouncing) {
        IsDebouncing = true;
        AsyncUtils.DelayCall (DebouncePeriodMsec, () => {
            IsDebouncing = false;
        });

        _execute ();
    }
}
 15
Author: cod3monk3y,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-05 18:09:07

Parece que el control de la creación de ambos objetos y su interdependencia necesita controlarse externamente, en lugar de entre las propias clases.

 7
Author: Adam Ralph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-13 10:57:29

Es de hecho un diseño muy malo, y mucho menos singleton por 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();

Esto, sin embargo, invocará bar() en un hilo separado. Si necesitas llamar a bar() en el hilo original, es posible que necesites mover bar() invocación a RunWorkerCompleted controlador o hacer un poco de hacking con SynchronizationContext.

 5
Author: Anton Gogolev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-12 13:25:56

Bueno, tendría que estar de acuerdo con el punto de "diseño"... pero probablemente puede usar un Monitor para que uno sepa cuando el otro está más allá de 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            
    }
 2
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-13 10:58:04
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!");
}
 2
Author: David O'Donoghue,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-10 15:06:30

Pensé que la solución perfecta sería tener un temporizador para manejar la acción retardada. 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 ordenación por columna. Pensé que un temporizador de un solo disparo (AutoReset = false) sería la solución, y funciona perfectamente. Y, FxCop no me dejará suprimir la advertencia!

 1
Author: Jim Mahaffey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-26 14:36:31

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ñarse. No es una buena idea.

 0
Author: ng5000,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-13 10:58:36

Basándose en 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 usando una clave única para los delegados. Porque si agregas el mismo delegado una segunda vez antes de que se dispare el primero, podrías tener un problema con el diccionario.

 0
Author: Pic Mickael,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-29 12:43:36
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));
}
 0
Author: Koray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-23 08:59:43

Esto funcionará en versiones anteriores de. NET
Contras: se ejecutará en su propio subproceso

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
 0
Author: altair,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-21 19:53:41