Delayed function calls
Asked Answered
K

12

121

Is there a nice simple method of delaying a function call whilst letting the thread continue executing?

e.g.

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
}

I'm aware that this can be achieved by using a timer and event handlers, but I was wondering if there is a standard c# way to achieve this?

If anyone is curious, the reason that this is required is that foo() and bar() are in different (singleton) classes which my need to call each other in exceptional circumstances. The problem being that this is done at initialisation so foo needs to call bar which needs an instance of the foo class which is being created... hence the delayed call to bar() to ensure that foo is fully instanciated.. Reading this back almost smacks of bad design !

EDIT

I'll take the points about bad design under advisement! I've long thought that I might be able to improve the system, however, this nasty situation only occurs when an exception is thrown, at all other times the two singletons co-exist very nicely. I think that I'm not going to messaround with nasty async-patters, rather I'm going to refactor the initialisation of one of the classes.

Kassandrakassaraba answered 13/2, 2009 at 10:51 Comment(4)
You need to fix it but not by using threads (or any other asyn practice for that matter)Arrogant
Having to use threads to synchronize object initialization is the sign that you should take another way. Orchestrator seems a better option.Peptic
Resurrection! -- Commenting on the design, you could make a choice to have a two-stage initialization. Drawing from the Unity3D API, there are Awake and Start phases. In the Awake phase, you configure yourself, and by the end of this phase all objects are initialized. During the Start phase the objects can begin communicating with each other.Corum
The accepted answer needs to be changedIrony
O
276

Thanks to modern C# 5/6 :)

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

public void bar()
{
    // do stuff
}
Ordinary answered 24/12, 2015 at 23:20 Comment(6)
This answer is awesome for 2 reasons. Code simplicity and the fact that Delay do NOT create a thread nor use the thread pool like other Task.Run or Task.StartNew... it's internally a timer.Carpophagous
A decent solution.Antenatal
Also note a slightly cleaner (IMO) equivalent version: Task.Delay(TimeSpan.FromSeconds(1)).ContinueWith(_ => bar());Crispi
@Carpophagous Actually it does use a different thread. Try accessing a UI element from it and it will trigger an exception.Irresolution
@Irresolution - If Zyo is correct that it runs on an already existing thread that runs timer events, then his point is that it does not consume extra resources creating a new thread, nor queuing to the thread pool. (Though I don't know whether creating a timer is significantly cheaper than queueing a task to thread pool - which ALSO doesn't create a thread, that being the whole point of the thread pool.)Mixie
Here's how you can continue on UI thread: Task.Delay(1000).ContinueWith(_ => { Application.Current.Dispatcher.Invoke(() => { bar(); });Levania
O
107

I've been looking for something like this myself - I came up with the following, although it does use a timer, it uses it only once for the initial delay, and doesn't require any Sleep calls ...

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
}

(thanks to Fred Deschenes for the idea of disposing the timer within the callback)

Obstinate answered 11/7, 2011 at 6:12 Comment(10)
I feel this is the best answer in general to delay a function call. No thread, no background working, no sleep. Timer are very efficient and memory/cpu wise.Carpophagous
@Zyo, thanks for your comment - yes timers are efficient, and this sort of delay is useful in many situations, especially when interfacing to something that is out of your control - which doesn't have any support for notification events.Obstinate
When do you Dispose of the Timer?Ultima
Reviving an old thread here, but the timer could be disposed like this : public static void CallWithDelay(Action method, int delay) { Timer timer = null; var cb = new TimerCallback((state) => { method(); timer.Dispose(); }); timer = new Timer(cb, null, delay, Timeout.Infinite); } EDIT : Well looks like we can't post code in comments... VisualStudio should format it properly when you copy/paste it anyway :PCantu
@Obstinate Wrong. Using the timer local variable from within the lambda that gets bound to the delegate object cb causes it to be hoisted onto an anon store (closure implementation detail) that will cause the Timer object to be reachable from the GC's perspective for as long as the TimerCallback delegate itself is reachable. In other words, the Timer object is guaranteed not to be garbage collected until after the delegate object is invoked by the thread pool.Baiss
@Baiss agreed - I stand corrected. I should have read his comment more carefully.Obstinate
@Baiss I've updated the answer to add the disposal of the timer object within the callback, as per FredDeschenes comment.Obstinate
If the local variable itself remains reachable, then might be safest to put "timer = null;" after the Dispose? Or does the Dispose remove the callback reference that is keeping the variable alive, so not needed? (I realize that once the Dispose is done, the Timer won't be holding any significant resources, so its just a question of whether a few bytes of managed memory keep hanging around.)Mixie
Still quite useful for Unity user since they haven't upgraded .NET to make Task.Delay() available.Hoist
The only thing I would like to add is that this timer IS using threadpool. It could be significant in some cases. learn.microsoft.com/en-us/dotnet/api/…Anisette
C
17

Aside from agreeing with the design observations of the previous commenters, none of the solutions were clean enough for me. .Net 4 provides Dispatcher and Task classes which make delaying execution on the current thread pretty 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 ();
    }
}

For context, I'm using this to debounce an ICommand tied to a left mouse button up on a UI element. Users are double clicking which was causing all kinds of havoc. (I know I could also use Click/DoubleClick handlers, but I wanted a solution that works with ICommands across the board).

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

        _execute ();
    }
}
Corum answered 5/3, 2014 at 18:9 Comment(0)
C
7

It sounds like the control of the creation of both these objects and their interdependence needs to controlled externally, rather than between the classes themselves.

Coumas answered 13/2, 2009 at 10:57 Comment(1)
+1, this does sound like you need an orchestrator of some sort and perhaps a factoryCascade
P
7

It's indeed a very bad design, let alone singleton by itself is bad design.

However, if you really do need to delay execution, here's what you may do:

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

This will, however, invoke bar() on a separate thread. If you need to call bar() in the original thread you might need to move bar() invocation to RunWorkerCompleted handler or do a bit of hacking with SynchronizationContext.

Periodontal answered 13/2, 2009 at 10:58 Comment(0)
S
3

Well, I'd have to agree with the "design" point... but you can probably use a Monitor to let one know when the other is past the critical section...

    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            
    }
Sivan answered 13/2, 2009 at 10:58 Comment(0)
S
3
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);

        }


    }

}

Usage:

DelayedDelegate.Add(MyMethod,5);

void MyMethod()
{
     MessageBox.Show("5 Seconds Later!");
}
Sclera answered 10/1, 2012 at 15:6 Comment(2)
I would advise to put some logic to avoid the timer to run every 250 milliseconds. First: You can increase the delay to 500 milliseconds because your minimum allowed interval is 1 second. Second: You could start the timer only when new delegates are added, and stop it when there are no more delegates. No reason to keep using CPU cycles when there is nothing to do. Third: you could set the timer interval to the minimum delay across all delegates. So it wakes up only when it needs to invoke a delegate, instead of waking up every 250 milliseconds to see if there is something to do.Grannie
MethodInvoker is a Windows.Forms object. Is there an alternative for Web developers please? ie: something that does not clash with System.Web.UI.WebControls.September
B
3

This will work either on older versions of .NET
Cons: will execute in its own thread

class CancellableDelay
    {
        Thread delayTh;
        Action action;
        int ms;

        public static CancellableDelay StartAfter(int milliseconds, Action action)
        {
            CancellableDelay result = new CancellableDelay() { ms = milliseconds };
            result.action = action;
            result.delayTh = new Thread(result.Delay);
            result.delayTh.Start();
            return result;
        }

        private CancellableDelay() { }

        void Delay()
        {
            try
            {
                Thread.Sleep(ms);
                action.Invoke();
            }
            catch (ThreadAbortException)
            { }
        }

        public void Cancel() => delayTh.Abort();

    }

Usage:

var job = CancellableDelay.StartAfter(1000, () => { WorkAfter1sec(); });  
job.Cancel(); //to cancel the delayed job
Betwixt answered 20/4, 2018 at 15:50 Comment(0)
T
1

I though the perfect solution would be to have a timer handle the delayed action. FxCop doesn't like when you have an interval less then one second. I need to delay my actions until AFTER my DataGrid has completed sorting by column. I figured a one-shot timer (AutoReset = false) would be the solution, and it works perfectly. AND, FxCop will not let me suppress the warning!

Torrid answered 26/1, 2012 at 14:36 Comment(0)
C
0

There is no standard way to delay a call to a function other than to use a timer and events.

This sounds like the GUI anti pattern of delaying a call to a method so that you can be sure the form has finished laying out. Not a good idea.

Cascade answered 13/2, 2009 at 10:58 Comment(0)
G
0

Building upon the answer from David O'Donoghue here is an optimized version of the Delayed Delegate:

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

The class could be slightly more improved by using a unique key for the delegates. Because if you add the same delegate a second time before the first one fired, you might get a problem with the dictionary.

Grannie answered 27/9, 2013 at 13:19 Comment(0)
P
0
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));
}
Photo answered 31/1, 2014 at 16:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.