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

前端 未结 12 1416
我在风中等你
我在风中等你 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:12

    I don't know which version of C# you are using. But I think you could accomplish this by using the Task library. It would then look something like that.

    public class PauseAndExecuter
    {
        public async Task Execute(Action action, int timeoutInMilliseconds)
        {
            await Task.Delay(timeoutInMilliseconds);
            action();
        }
    }
    
    0 讨论(0)
  • 2020-12-13 09:12

    Use Microsoft's Reactive Framework (NuGet "System.Reactive") and then you can do this:

    protected void Execute(Action action, int timeout_ms)
    {
        Scheduler.Default.Schedule(TimeSpan.FromMilliseconds(timeout_ms), action);
    }
    
    0 讨论(0)
  • 2020-12-13 09:13

    There is nothing built-in to .Net 4 to do this nicely. Thread.Sleep or even AutoResetEvent.WaitOne(timeout) are not good - they will tie up thread pool resources, I have been burned trying this!

    The lightest weight solution is to use a timer - particularly if you will have many tasks to throw at it.

    First make a simple scheduled task class:

    class ScheduledTask
    {
        internal readonly Action Action;
        internal System.Timers.Timer Timer;
        internal EventHandler TaskComplete;
    
        public ScheduledTask(Action action, int timeoutMs)
        {
            Action = action;
            Timer = new System.Timers.Timer() { Interval = timeoutMs };
            Timer.Elapsed += TimerElapsed;            
        }
    
        private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Timer.Stop();
            Timer.Elapsed -= TimerElapsed;
            Timer = null;
    
            Action();
            TaskComplete(this, EventArgs.Empty);
        }
    }
    

    Then, create a scheduler class - again, very simple:

    class Scheduler
    {        
        private readonly ConcurrentDictionary<Action, ScheduledTask> _scheduledTasks = new ConcurrentDictionary<Action, ScheduledTask>();
    
        public void Execute(Action action, int timeoutMs)
        {
            var task = new ScheduledTask(action, timeoutMs);
            task.TaskComplete += RemoveTask;
            _scheduledTasks.TryAdd(action, task);
            task.Timer.Start();
        }
    
        private void RemoveTask(object sender, EventArgs e)
        {
            var task = (ScheduledTask) sender;
            task.TaskComplete -= RemoveTask;
            ScheduledTask deleted;
            _scheduledTasks.TryRemove(task.Action, out deleted);
        }
    }
    

    It can be called as follows - and is very lightweight:

    var scheduler = new Scheduler();
    
    scheduler.Execute(() => MessageBox.Show("hi1"), 1000);
    scheduler.Execute(() => MessageBox.Show("hi2"), 2000);
    scheduler.Execute(() => MessageBox.Show("hi3"), 3000);
    scheduler.Execute(() => MessageBox.Show("hi4"), 4000);
    
    0 讨论(0)
  • 2020-12-13 09:15

    My example:

    void startTimerOnce()
    {
       Timer tmrOnce = new Timer();
       tmrOnce.Tick += tmrOnce_Tick;
       tmrOnce.Interval = 2000;
       tmrOnce.Start();
    }
    
    void tmrOnce_Tick(object sender, EventArgs e)
    {
       //...
       ((Timer)sender).Dispose();
    }
    
    0 讨论(0)
  • 2020-12-13 09:24

    treze's code is working just fine. This might help the ones who have to use older .NET versions:

    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 讨论(0)
  • 2020-12-13 09:25

    I use this method to schedule a task for a specific time:

    public void ScheduleExecute(Action action, DateTime ExecutionTime)
    {
        Task WaitTask = Task.Delay(ExecutionTime.Subtract(DateTime.Now));
        WaitTask.ContinueWith(() => action());
        WaitTask.Start();
    }
    

    It should be noted that this only works for about 24 days out because of int32 max value.

    0 讨论(0)
提交回复
热议问题