[C#]Executing Tasks Repeatedly at Specific Intervals in C#

目次

Overview

To call a specific method repeatedly at a fixed interval, use the System.Threading.Timer class. This class uses a thread from the thread pool to execute the callback. This allows you to perform background tasks like logging or status monitoring without blocking the main thread.

Specifications (Input/Output)

  • Input: Delay before starting (milliseconds) and the repetition interval (milliseconds).
  • Output: The current time is printed to the console at every specified interval.
  • Behavior: The process continues until the user presses the Enter key, at which point the timer stops and the application exits.

Basic Usage

Specify the method to execute, any data to pass, the initial delay, and the execution interval in the constructor.

// Starts after 1 second (1000ms) and executes CallbackMethod every 2 seconds (2000ms)
Timer timer = new Timer(CallbackMethod, null, 1000, 2000);

Full Code

This console application creates a timer and displays the time periodically on a separate thread. The main thread waits for the user to stop the program using Console.ReadLine().

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Application Started");

        // Create the timer
        // 1st arg: Method to execute (TimerCallback)
        // 2nd arg: Data to pass to the method (null in this case)
        // 3rd arg: Delay before the first execution (milliseconds) -> Start after 1000ms
        // 4th arg: Repetition interval (milliseconds) -> Execute every 2000ms thereafter
        using (var timer = new Timer(DoSomething, null, 1000, 2000))
        {
            Console.WriteLine("Press Enter to stop the timer and exit...");
            
            // Wait for user input (otherwise the program terminates immediately)
            Console.ReadLine();

            // Stop the timer
            // Using Timeout.Infinite (-1) disables the timer
            timer.Change(Timeout.Infinite, Timeout.Infinite);
            
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Timer Stop Instruction Received");
        }
        // Exiting the 'using' block calls Dispose() and releases timer resources

        Console.WriteLine("Application Terminated.");
    }

    // Callback method executed periodically
    // Must accept an 'object?' type argument
    static void DoSomething(object? state)
    {
        // Display current thread ID to confirm it is different from the main thread
        int threadId = Thread.CurrentThread.ManagedThreadId;
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Periodic Task Executing (Thread: {threadId})");
    }
}

Execution Result Example Application Started

Press Enter to stop the timer and exit… Periodic Task Executing (Thread: 4) Periodic Task Executing (Thread: 5) Periodic Task Executing (Thread: 4) (User presses Enter) Timer Stop Instruction Received Application Terminated.

Customization Points

  • Changing the Schedule: You can use the timer.Change(dueTime, period) method to adjust intervals or pause the timer while it is running.
  • Passing State Objects: By passing an object to the second argument (state) of the constructor, you can receive it as the arg in your callback method. This is useful for passing counters or configuration classes.

Points of Caution

  • Re-entrancy: If the task takes longer than the interval, a new execution might start on another thread before the previous one finishes. To prevent this, you should use a lock or set the timer to “one-shot” (no interval) and reschedule it after the task completes.
  • Exception Handling: Unhandled exceptions in the callback method will crash the entire process. Always wrap your logic in a try-catch block.
  • Garbage Collection (GC): If you create a Timer as a local variable and exit the scope without a using block or waiting mechanism, the GC may collect the timer, causing it to stop unexpectedly. Ensure the timer instance is maintained in scope or held in a field.

Application

Preventing Re-entrancy

This implementation prevents overlapping execution by stopping the next timer until the current process is finished.

class SafeTimer
{
    private Timer _timer;

    public void Start()
    {
        // Initially execute once after 1 second (Period set to Infinite)
        _timer = new Timer(Callback, null, 1000, Timeout.Infinite);
    }

    private void Callback(object state)
    {
        try
        {
            Console.WriteLine("Starting heavy process...");
            Thread.Sleep(3000); // Simulating processing time
            Console.WriteLine("Process Finished");
        }
        finally
        {
            // Reschedule the next timer (after 2 seconds) once the task is done
            _timer?.Change(2000, Timeout.Infinite);
        }
    }
}

Summary

System.Threading.Timer is a lightweight timer suitable for server-side or background tasks. If you need to manipulate UI components in Windows Forms or WPF, use a UI-specific timer like System.Windows.Forms.Timer or switch threads using Invoke. Additionally, because there is a risk of overlapping execution, choose between locks or one-shot execution patterns based on your specific needs.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次