Domanda

Esiste un metodo semplice e carino per ritardare una chiamata di funzione lasciando che il thread continui l'esecuzione?

per esempio.

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
}

Sono consapevole che ciò può essere ottenuto utilizzando un timer e gestori di eventi, ma mi chiedevo se esiste un modo C# standard per raggiungere questo obiettivo?

Se qualcuno è curioso, il motivo per cui ciò è richiesto è che foo() e bar() si trovano in classi diverse (singleton) che ho bisogno di chiamarsi a vicenda in circostanze eccezionali.Il problema è che questo viene fatto al momento dell'inizializzazione, quindi foo deve chiamare bar che necessita di un'istanza della classe foo che viene creata...da qui la chiamata ritardata a bar() per garantire che foo sia completamente istanziato.Rileggendolo sembra quasi di cattivo design!

MODIFICARE

Prenderò in considerazione i punti relativi al cattivo design!Ho pensato a lungo che avrei potuto migliorare il sistema, tuttavia questa brutta situazione soltanto si verifica quando viene lanciata un'eccezione, in tutti gli altri casi i due singleton coesistono molto bene.Penso che non andrò in giro con brutti schemi asincroni, piuttosto effettuerò il refactoring dell'inizializzazione di una delle classi.

È stato utile?

Soluzione

Grazie alla moderna C # 5/6:)

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

public void bar()
{
    // do stuff
}

Altri suggerimenti

Sono stato alla ricerca di qualcosa di simile me - mi si avvicinò con la seguente, anche se fa uso di un timer, utilizza solo una volta per il ritardo iniziale, e non richiede alcuna chiamata 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
}

(grazie a Fred Deschenes per l'idea di disporre il temporizzatore all'interno della callback)

A parte essere d'accordo con le osservazioni progettuali dei commentatori precedenti, nessuna delle soluzioni era abbastanza pulita per me..Net 4 fornisce Dispatcher E Task classi che ritardano l'esecuzione sul thread corrente abbastanza semplice:

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 ();
    }
}

Per contesto, lo sto usando per rimbalzare un ICommand legato al pulsante sinistro del mouse su un elemento dell'interfaccia utente.Gli utenti fanno doppio clic, causando ogni tipo di caos.(So ​​che potrei anche usare Click/DoubleClick gestori, ma volevo una soluzione che funzionasse con ICommandè a tutti i livelli).

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

        _execute ();
    }
}

Sembra che il controllo della creazione di entrambi questi oggetti e la loro interdipendenza deve comando esterno, piuttosto che tra le classi stesse.

E 'davvero un pessimo disegno, per non parlare di Singleton di per sé è cattiva progettazione.

Tuttavia, se si ha realmente bisogno di ritardare l'esecuzione, ecco cosa si può fare:

BackgroundWorker barInvoker = new BackgroundWorker();
barInvoker.DoWork += delegate
    {
        Thread.Sleep(TimeSpan.FromSeconds(1));
        bar();
    };
barInvoker.RunWorkerAsync();

Ciò, tuttavia, invocare bar() su un thread separato. Se avete bisogno di chiamare bar() nel thread originale potrebbe essere necessario spostare bar() invocazione a RunWorkerCompleted gestore o di fare un po 'di hacking SynchronizationContext.

Beh, avrei dovuto essere d'accordo con il punto di "design" ... ma probabilmente è possibile utilizzare un monitor per lasciare a sapere quando l'altro è passato, la sezione critica ...

    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);

        }


    }

}

Utilizzo:

DelayedDelegate.Add(MyMethod,5);

void MyMethod()
{
     MessageBox.Show("5 Seconds Later!");
}

ho anche se la soluzione perfetta sarebbe quella di avere un timer gestire l'azione ritardata. FxCop non come quando si dispone di un intervallo di meno di un secondo. Ho bisogno di ritardare le mie azioni fino a quando, dopo la mia DataGrid ha completato l'ordinamento per colonna. Ho pensato che un timer one-shot (AutoReset = false) sarebbe la soluzione, e funziona perfettamente. E, FxCop non mi permette di eliminare l'avviso!

Questo funziona sia su versioni precedenti di .NET
Contro: verrà eseguito nel proprio thread

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();

    }

Utilizzo:

var job = CancelableDelay.StartAfter(1000, () => { WorkAfter1sec(); });  
job.Cancel(); //to cancel the delayed job

Non esiste un modo standard per ritardare una chiamata a una funzione diversa per utilizzare un timer e gli eventi.

Questo suona come il modello GUI contro di ritardare una chiamata a un metodo in modo da poter essere sicuri che il modulo ha terminato la posa fuori. Non è una buona idea.

Edificio sulla risposta da David O'Donoghue qui è una versione ottimizzata del Delegato ritardata:

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 classe potrebbe essere un po 'più migliorata utilizzando una chiave univoca per i delegati. Perché se si aggiunge la stessa delegato una seconda volta prima del primo licenziati, si potrebbe ottenere un problema con il dizionario.

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));
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top