Large Number of Timers

Viewed 7786

I need to write a component that receives an event (the event has a unique ID). Each event requires me to send out a request. The event specifies a timeout period, which to wait for a response from the request.

If the response comes before the timer fires, great, I cancel the timer. If the timer fires first, then the request timed out, and I want to move on.

This timeout period is specified in the event, so it's not constant. The expected timeout period is in the range of 30 seconds to 5 minutes.

I can see two ways of implementing this.

  1. Create a timer for each event and put it into a dictionary linking the event to the timer.
  2. Create an ordered list containing the DateTime of the timeout, and a new thread looping every 100ms to check if something timed out.

Option 1 would seem like the easiest solution, but I'm afraid that creating so many timers might not be a good idea because timers might be too expensive. Are there any pitfalls when creating a large number of timers? I suspect that in the background, the timer implementation might actually be an efficient implementation of Option 2. If this option is a good idea, which timer should I use? System.Timers.Timer or System.Threading.Timer.

Option 2 seems like more work, and may not be an efficient solution compared to Option 1.

Update

The maximum number of timers I expect is in the range of 10000, but more likely in the range of 100. Also, the normal case would be the timer being canceled before firing.

Update 2

I ran a test using 10K instances of System.Threading.Timer and System.Timers.Timer, keeping an eye on thread count and memory. System.Threading.Timer seems to be "lighter" compared to System.Timers.Timer judging by memory usage, and there was no creation of excessive number of threads for both timers (ie - thread pooling working properly). So I decided to go ahead and use System.Threading.Timer.

5 Answers

I think Task.Delay is a really good option. Here is the test code for measuring how many concurrent tasks can be executed in different delay times. This code is also calculating error statistics for waiting time accuracy.

    static async Task Wait(int delay, double[] errors, int index)
    {
        var sw = new Stopwatch();
        sw.Start();
        await Task.Delay(delay);
        sw.Stop();
        errors[index] = Math.Abs(sw.ElapsedMilliseconds - delay);
    }

    static void Main(string[] args)
    {
        var trial = 100000;
        var minDelay = 1000;
        var maxDelay = 5000;

        var errors = new double[trial];
        var tasks = new Task[trial];
        var rand = new Random();
        var sw = new Stopwatch();

        sw.Start();
        for (int i = 0; i < trial; i++)
        {
            var delay = rand.Next(minDelay, maxDelay);
            tasks[i] = Wait(delay, errors, i);
        }
        sw.Stop();

        Console.WriteLine($"{trial} tasks started in {sw.ElapsedMilliseconds} milliseconds.");

        Task.WaitAll(tasks);

        Console.WriteLine($"Avg Error: {errors.Average()}");
        Console.WriteLine($"Min Error: {errors.Min()}");
        Console.WriteLine($"Max Error: {errors.Max()}");

        Console.ReadLine();
    }

You may change the parameters to see different results. Here are several results in milliseconds:

100000 tasks started in 9353 milliseconds.

Avg Error: 9.10898

Min Error: 0

Max Error: 110

Related