it-swarm.com.de

Verzögerte Funktionsaufrufe

Gibt es eine einfache Methode, um einen Funktionsaufruf zu verzögern, während der Thread weiterhin ausgeführt wird?

z.B.

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
}

Ich bin mir bewusst, dass dies durch die Verwendung eines Zeitgebers und von Ereignishandlern erreicht werden kann, aber ich habe mich gefragt, ob es eine Standardmethode gibt, um dies zu erreichen.

Wenn jemand neugierig ist, besteht der Grund dafür, dass foo () und bar () in verschiedenen (Singleton-) Klassen sind, die ich unter außergewöhnlichen Umständen aufrufen muss. Das Problem ist, dass dies bei der Initialisierung geschieht, so dass foo eine Leiste aufrufen muss, die eine Instanz der foo-Klasse benötigt, die erstellt wird ... daher der verzögerte Aufruf von bar (), um sicherzustellen, dass foo vollständig instanziiert ist fast riecht nach schlechtem Design!

EDIT

Ich werde die Punkte über schlechtes Design unter Beratung nehmen! Ich habe lange gedacht, dass ich vielleicht das System verbessern könnte, jedoch tritt diese unangenehme Situation nur auf, wenn eine Ausnahme ausgelöst wird. Zu allen anderen Zeiten koexistieren die beiden Singletons sehr schön. Ich denke, ich werde mich nicht mit bösen Async-Mustern herumschlagen, sondern ich werde die Initialisierung einer der Klassen umgestalten.

65
TK.

Dank modernem C # 5/6 :)

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

public void bar()
{
    // do stuff
}
114
Korayem

Ich habe selbst nach so etwas gesucht - ich habe mir folgendes ausgedacht, obwohl es einen Timer verwendet, er verwendet ihn nur einmal für die anfängliche Verzögerung und erfordert keine Sleep-Aufrufe ...

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
}

(Danke an Fred Deschenes für die Idee, den Timer innerhalb des Rückrufs zu entsorgen)

91
dodgy_coder

Abgesehen davon, dass ich mit den Design-Beobachtungen der vorherigen Kommentatoren übereinstimmte, war keine der Lösungen für mich sauber genug. .Net 4 bietet Dispatcher und Task Klassen, die die Ausführung von im aktuellen Thread ziemlich einfach machen:

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

Für den Kontext verwende ich dies, um eine ICommand zu entprellen, die an eine linke Maustaste auf einem UI-Element gebunden ist. Benutzer doppelklicken, was alle Arten von Chaos verursacht hat. (Ich weiß, ich könnte auch Click/DoubleClick Handler verwenden, aber ich wollte eine Lösung, die mit ICommands überall funktioniert).

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

        _execute ();
    }
}
15
cod3monk3y

Es klingt wie die Kontrolle über die Erstellung dieser beiden Objekte und ihre gegenseitige Abhängigkeit muss von außen gesteuert werden und nicht zwischen den Klassen selbst.

7
Adam Ralph

Es ist in der Tat ein sehr schlechtes Design, ganz zu schweigen von Singleton an sich ist schlechtes Design.

Wenn Sie jedoch die Ausführung wirklich verzögern müssen, können Sie Folgendes tun:

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

Dies wird jedoch bar() in einem separaten Thread aufrufen. Wenn Sie bar() im ursprünglichen Thread aufrufen müssen, müssen Sie den Aufruf von bar() in den RunWorkerCompleted-Handler verschieben oder mit SynchronizationContext ein wenig hacken.

5
Anton Gogolev

Nun, ich muss dem "Design" -Punkt zustimmen ... aber Sie können wahrscheinlich einen Monitor verwenden, um einen wissen zu lassen, wenn der andere den kritischen Abschnitt überschritten hat ...

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

        }


    }

}

Verwendungszweck:

DelayedDelegate.Add(MyMethod,5);

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

Ich denke, die perfekte Lösung wäre, einen Timer für die verzögerte Aktion zu haben. FxCop mag es nicht, wenn Sie ein Intervall von weniger als einer Sekunde haben .. Ich muss meine Aktionen so lange verzögern, bis das DataGrid die Sortierung abgeschlossen hat Nach Spalte . Ich dachte mir, ein One-Shot-Timer (AutoReset = false) wäre die Lösung, und es funktioniert einwandfrei . UND FxCop lässt mich die Warnung nicht unterdrücken!

1
Jim Mahaffey

Dies funktioniert entweder mit älteren Versionen von .NET
Cons: wird in einem eigenen Thread ausgeführt

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

    }

Verwendungszweck:

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

Es gibt keine Standardmethode, um den Aufruf einer Funktion zu verzögern, außer einen Timer und Ereignisse zu verwenden.

Das klingt wie das GUI-Anti-Pattern, bei dem der Aufruf einer Methode verzögert wird, sodass Sie sicher sein können, dass das Formular fertig angeordnet ist. Keine gute Idee.

0
ng5000

Aufbauend auf der Antwort von David O'Donoghue ist hier eine optimierte Version des verzögerten Delegierten:

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

Die Klasse könnte durch die Verwendung eines eindeutigen Schlüssels für die Delegierten etwas verbessert werden. Wenn Sie denselben Delegaten ein zweites Mal hinzufügen, bevor der erste ausgelöst wird, könnten Sie ein Problem mit dem Wörterbuch haben.

0
Pic Mickael
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
Koray