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 thread safe. I also really, really want to avoid Thread.Sleep().
So far, the best I can do is this:
long currentKey = 0;
ConcurrentDictionary<long, Timer> timers = new ConcurrentDictionary<long, Timer>();
protected void Execute(Action action, int timeout_ms)
{
long currentKey = Interlocked.Increment(ref currentKey);
Timer t = new Timer(
(key) =>
{
action();
Timer lTimer;
if(timers.TryRemove((long)key, out lTimer))
{
lTimer.Dispose();
}
}, currentKey, Timeout.Infinite, Timeout.Infinite
);
timers[currentKey] = t;
t.Change(timeout_ms, Timeout.Infinite);
}
The problem is that calling Dispose() from the callback itself cannot be good. I am unsure if it is safe to "fall off" the end, i.e. Timers are considered live while their lambdas are executing, but even if this is the case I'd rather dispose it properly.
The "fire once with a delay" seems like such a common problem that there should be an easy way to do this, probably some other library in System.Threading I am missing, but right now the only solution I can think of is modification of the above with a dedicated cleanup task running on an interval. Any advice?
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:
Then, create a scheduler class - again, very simple:
It can be called as follows - and is very lightweight:
treze's code is working just fine. This might help the ones who have to use older .NET versions:
This may be a little bit late, but here is the solution I am currently using to handle delayed execution:
You can use it like this:
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?).
I use this method to schedule a task for a specific time:
It should be noted that this only works for about 24 days out because of int32 max value.
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).