Pregunta

¿Hay un método simple agradable de retrasar una llamada de función, mientras que dejar que el hilo de continuar con la ejecución?

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 manera estándar de C # para lograr esto?

Si alguien tiene curiosidad, la razón de que esto es necesario es que foo () y bar () se encuentran en diferentes clases (Singleton), que mi necesidad de llamar a los demás en circunstancias excepcionales. El problema es que esto se hace en la inicialización de modo foo necesita llamar bar que necesita una instancia de la clase foo que está siendo creado ... de ahí la llamada retardada a bar () para asegurarse de que fu está completamente instanciado .. La lectura de esta vuelta casi suena a un mal diseño!

editar

Me quedo con los puntos sobre el mal diseño bajo consideración! Durante mucho tiempo he idea de que podría ser capaz de mejorar el sistema, sin embargo, esta situación desagradable solamente tiene lugar cuando se produce una excepción, el resto del tiempo los dos únicos coexistir muy bien. Creo que no voy a messaround con desagradables asincrónicos-patrones, en lugar Voy a refactorizar la inicialización de una de las clases.

¿Fue útil?

Solución

Gracias a la moderna C # 5/6:)

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

public void bar()
{
    // do stuff
}

Otros consejos

He estado buscando algo como esto mismo - me ocurrió lo siguiente, a pesar de que hace uso de un temporizador, se utiliza sólo una vez por el retraso inicial, y no requiere ninguna llamada 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 a la idea de disponer el temporizador dentro de la devolución de llamada)

Además de estar de acuerdo con las observaciones de diseño de los comentaristas anteriores, ninguna de las soluciones eran lo suficientemente limpia para mí. .Net 4 proporciona Dispatcher y Task clases que hacen retrasar la ejecución en el hilo actual 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 este para el rebote de un ICommand atada a un botón izquierdo del ratón sobre un elemento de interfaz de usuario. Los usuarios son doble clic que estaba causando todo tipo de estragos. (Sé que también podría utilizar manipuladores Click / DoubleClick, pero quería una solución que funciona con ICommands a través del tablero).

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

        _execute ();
    }
}

Parece que el control de la creación de estos dos objetos y su interdependencia necesita controlado externamente, en lugar de entre las clases mismas.

Es de hecho un diseño muy malo, por no hablar de Singleton por sí mismo es un mal diseño.

Sin embargo, si usted realmente necesita para 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 necesita llamar a bar() en el hilo original puede que tenga que mover la invocación bar() a RunWorkerCompleted controlador o hacer un poco de piratería con SynchronizationContext.

Bueno, tendría que estar de acuerdo con el punto "de diseño" ... pero es probable que pueda usar un monitor para que uno sabe 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            
    }
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!");
}

I, aunque la solución perfecta sería tener un temporizador de manejar la acción retardada. FxCop no le gusta cuando se tiene un intervalo de menos de un segundo. Necesito mis acciones retrasar hasta después de mi DataGrid ha completado la clasificación por columna. Me imaginé un temporizador de una sola vez (AutoReset = false) sería la solución, y funciona perfectamente. Y, FxCop no me permite suprimir la advertencia!

Esto funciona bien 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

No hay forma estándar para retrasar una llamada a una función que la de utilizar un temporizador y eventos.

Esto suena como el patrón contra la GUI de retrasar una llamada a un método para que pueda estar seguro de que el formulario ha terminado de poner a cabo. No es una buena idea.

Sobre la base de la respuesta de David O'Donoghue aquí es una versión optimizada del Delegado retardado:

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 ser un poco más mejorada usando una clave única para los delegados. Porque si se agrega el mismo delegado por segunda vez antes de que el primero de ellos despedidos, es posible obtener 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));
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top