Best way to create a "run-once" time delayed function in C#
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();
}
}
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);
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.