nish91
nish91

Reputation: 143

In .net does a new thread get created for each System.Thread.Timer object in a process?

I want to execute methods at different intervals and was looking at using the Timer class to schedule this. However, I wanted to understand if the Timer spun up a new thread for each new schedule which could then potentially impact performance of the application

Upvotes: 0

Views: 181

Answers (2)

Enigmativity
Enigmativity

Reputation: 117175

Dai's done a good job of answering the question regarding timers and threads.

I thought I'd give you an alternative way of writing your code. You should use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq; - then you can do this:

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

EventLoopScheduler els = new EventLoopScheduler();

els.Schedule(() => Console.WriteLine(Thread.CurrentThread.ManagedThreadId));

IObservable<string> pings = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(30), els).Select(x => "Ping!");
IObservable<string> pongs = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(20), els).Select(x => "Pong!");
IObservable<string> pangs = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(10), els).Select(x => "Pang!");

IObservable<string> query = Observable.Merge(els, pings, pongs, pangs);

IDisposable subscription = query.Subscribe(x => Console.WriteLine($"{x} ({Thread.CurrentThread.ManagedThreadId})"));

Console.ReadLine();

subscription.Dispose();
els.Dispose();

The EventLoopScheduler creates a single, dedicated, re-usable thread that you can used until you call .Dispose() on it.

Both Observable.Timer and Observable.Merge allow you to specify that you want to use the EventLoopScheduler to ensure that the code is run on that thread.

Upvotes: 2

Dai
Dai

Reputation: 155658

For System.Threading.Timer:

Short-answer: 90% of the time: no. It uses the Thread Pool to get an existing thread (that isn't doing anything).

Long-answer: Possibly! If all threads in the pool are busy then a new thread will need to be created by the OS and added to the pool and then used by the Timer.

https://learn.microsoft.com/en-us/dotnet/api/system.threading.timer?view=netframework-4.8

Provides a mechanism for executing a method on a thread pool thread at specified intervals. This class cannot be inherited.

For other timer types:

  • System.Windows.Forms.Timer triggers a new Win32 Window Message to be sent to the Form at a set interval. It does not use a dedicated thread nor a pool thread, instead it uses Win32's SetTimer function.

  • System.Timers.Timer will also use a pool thread by default (just like System.Threading.Timer) but lets you perform thread synchronization. See https://learn.microsoft.com/en-us/dotnet/api/system.timers.timer?view=netframework-4.8

A single-threaded alternative using coroutines:

I recommend you look at using await Task.Delay instead - as it won't cause a new thread to be used (remember that Tasks are not Threads) - though if you do use Task.Run to run the coroutine in a pool thread then it may run on a new thread:

public static class Foo
{
    public static async Task<Int32> Main( String[] args )
    {
        Task loop30 = this.Every30Seconds();
        Task loop20 = this.Every20Seconds();
        Taks loop10 = this.Every10Seconds();

        await Task.WhenAll( loop30, loop20, loop10 );

        return 0;
    }

    public static async Task Every30Seconds()
    {
        while( true )
        {
            Console.WriteLine("Ping!");

            await Task.Delay( 30 * 1000 );
        }
    }

    public static async Task Every20Seconds()
    {
        while( true )
        {
            Console.WriteLine("Pong!");

            await Task.Delay( 20 * 1000 );
        }
    }

    public static async Task Every10Seconds()
    {
        while( true )
        {
            Console.WriteLine("Pang!");

            await Task.Delay( 10 * 1000 );
        }
    }
}

Upvotes: 4

Related Questions