Best way to create a “run-once” time delayed function in C#

前端 未结 12 1417
我在风中等你
我在风中等你 2020-12-13 08:54

I am trying to create a function that takes in an Action and a Timeout, and executes the Action after the Timeout. The function is to be non-blocking. The function must be

相关标签:
12条回答
  • 2020-12-13 09:25

    The model you have, using a one-shot timer, is definitely the way to go. You certainly don't want to create a new thread for every one of them. You could have a single thread and a priority queue of actions keyed on time, but that's needless complexity.

    Calling Dispose in the callback probably isn't a good idea, although I'd be tempted to give it a try. I seem to recall doing this in the past, and it worked okay. But it's kind of a wonky thing to do, I'll admit.

    You can just remove the timer from the collection and not dispose it. With no references to the object, it will be eligible for garbage collection, meaning that the Dispose method will be called by the finalizer. Just not as timely as you might like. But it shouldn't be a problem. You're just leaking a handle for a brief period. As long as you don't have thousands of these things sitting around un-disposed for a long period, it's not going to be a problem.

    Another option is to have a queue of timers that remains allocated, but deactivated (i.e. their timeout and intervals set to Timeout.Infinite). When you need a timer, you pull one from the queue, set it, and add it to your collection. When the timeout expires, you clear the timer and put it back on the queue. You can grow the queue dynamically if you have to, and you could even groom it from time to time.

    That'll prevent you from leaking one timer for every event. Instead, you'll have a pool of timers (much like the Thread Pool, no?).

    0 讨论(0)
  • 2020-12-13 09:25

    Why not simply invoke your action parameter itself in an asynchronous action?

    Action timeoutMethod = () =>
      {
           Thread.Sleep(timeout_ms);
           action();
      };
    
    timeoutMethod.BeginInvoke();
    
    0 讨论(0)
  • 2020-12-13 09:25

    This may be a little bit late, but here is the solution I am currently using to handle delayed execution:

    public class OneShotTimer
    {
    
        private volatile readonly Action _callback;
        private OneShotTimer(Action callback, long msTime)
        {
            _callback = callback;
            var timer = new Threading.Timer(TimerProc);
            timer.Change(msTime, Threading.Timeout.Infinite);
        }
    
        private void TimerProc(object state)
        {
            try {
                // The state object is the Timer object. 
                ((Threading.Timer)state).Dispose();
                _callback.Invoke();
            } catch (Exception ex) {
                // Handle unhandled exceptions
            }
        }
    
        public static OneShotTimer Start(Action callback, TimeSpan time)
        {
            return new OneShotTimer(callback, Convert.ToInt64(time.TotalMilliseconds));
        }
        public static OneShotTimer Start(Action callback, long msTime)
        {
            return new OneShotTimer(callback, msTime);
        }
    }
    

    You can use it like this:

    OneShotTimer.Start(() => DoStuff(), TimeSpan.FromSeconds(1))
    
    0 讨论(0)
  • 2020-12-13 09:27

    Documentation clearly states that System.Timers.Timer has AutoReset property made just for what you are asking:

    https://msdn.microsoft.com/en-us/library/system.timers.timer.autoreset(v=vs.110).aspx

    0 讨论(0)
  • 2020-12-13 09:30

    If you don't care much about the granularity of time, you can create one timer that ticks every second and checks for expired Actions that need to be queued on the ThreadPool. Just use the stopwatch class to check for timeout.

    You can use your current approach, except your Dictionary will have Stopwatch as its Key and Action as its Value. Then you just iterate on all the KeyValuePairs and find the Stopwatch that expires, queue the Action, then remove it. You'll get better performance and memory usage from a LinkedList however (since you'll be enumerating the whole thing every time and removing an item is easier).

    0 讨论(0)
  • 2020-12-13 09:31

    This seems to work for me. It allows me to invoke _connection.Start() after a 15 second delay. The -1 millisecond parameter just says don't repeat.

    // Instance or static holder that won't get garbage collected (thanks chuu)
    System.Threading.Timer t;
    
    // Then when you need to delay something
    var t = new System.Threading.Timer(o =>
                {
                    _connection.Start(); 
                },
                null,
                TimeSpan.FromSeconds(15),
                TimeSpan.FromMilliseconds(-1));
    
    0 讨论(0)
提交回复
热议问题