مقدمه

در بسیاری از برنامه‌ها، نیاز داریم تا یک عملیات خاص را نه به صورت مداوم، بلکه در فواصل زمانی معین و به صورت دوره‌ای اجرا کنیم. برای مثال، بررسی وضعیت یک سرویس هر دقیقه، ذخیره‌ی خودکار یک سند هر پنج دقیقه، یا به‌روزرسانی داده‌ها از یک منبع خارجی هر ساعت. ایجاد یک نخ (Thread) که با استفاده از Thread.Sleep در یک حلقه منتظر بماند، برای این کار بسیار ناکارآمد است، زیرا یک نخ را برای تمام مدت به طور کامل اشغال می‌کند. .NET برای این سناریو، یک مکانیزم بسیار بهینه‌تر به نام تایمر (Timer) فراهم کرده است. یک تایمر به ما اجازه می‌دهد تا یک متد (که به آن callback گفته می‌شود) را برای اجرا در آینده یا در فواصل زمانی منظم، زمان‌بندی کنیم.

کلاس `System.Threading.Timer`

کلاس System.Threading.Timer ابزار اصلی برای کار با تایمرها در سطح پایین است. این تایمر بسیار سبک و کارآمد است، زیرا برای اجرای متد callback، از نخ‌های موجود در استخر نخ (Thread Pool) استفاده می‌کند. این بدان معناست که تایمر در زمان انتظار، هیچ نخی را اشغال نمی‌کند و تنها زمانی یک نخ از استخر را به کار می‌گیرد که زمان اجرای callback فرا رسیده باشد.

سازنده‌ی تایمر

سازنده‌ی اصلی این کلاس، چهار پارامتر کلیدی را می‌پذیرد:

public Timer(TimerCallback callback, object state, int dueTime, int period);
  • callback: یک نماینده از نوع TimerCallback (که امضای void Callback(object state) را دارد). این همان متدی است که قرار است به صورت دوره‌ای اجرا شود.
  • state: یک شیء دلخواه که می‌توانید به متد callback خود ارسال کنید تا در هر بار اجرا به آن دسترسی داشته باشید. اگر نیازی به آن ندارید، می‌توانید `null` پاس دهید.
  • dueTime: مدت زمان انتظار (به میلی‌ثانیه) قبل از اولین اجرای callback. مقدار `0` به معنای اجرای فوری است.
  • period: فاصله‌ی زمانی (به میلی‌ثانیه) بین اجراهای بعدی callback. اگر می‌خواهید تایمر فقط یک بار اجرا شود، از Timeout.Infinite استفاده کنید.

ایجاد یک تایمر ساده

بیایید یک تایمر بسازیم که هر دو ثانیه، زمان فعلی را در کنسول چاپ کند.

Copy Icon Program.cs
using System.Threading;

// 1. Define the callback method. It must match the TimerCallback delegate signature.
static void PrintTime(object state)
{
    Console.WriteLine($"Time is: {DateTime.Now.ToLongTimeString()}, State: {state}");
}

// --- In Main method ---
Console.WriteLine("Starting timer. Press Enter to exit.");

// 2. Create the timer.
// It will call PrintTime immediately (dueTime: 0),
// and then every 2 seconds (period: 2000).
Timer myTimer = new Timer(PrintTime, "MyTimer", 0, 2000);

// Keep the main thread alive, otherwise the application will exit.
Console.ReadLine();

// It's good practice to dispose of the timer when you're done.
myTimer.Dispose();

در این مثال، متد PrintTime هر دو ثانیه یک بار توسط یک نخ از Thread Pool فراخوانی می‌شود. از آنجایی که نخ‌های تایمر به صورت پس‌زمینه (background) هستند، باید با Console.ReadLine() نخ اصلی را زنده نگه داریم تا برنامه بلافاصله بسته نشود.

مدیریت طول عمر تایمر

شما می‌توانید رفتار یک تایمر را پس از ایجاد آن نیز تغییر دهید یا آن را متوقف کنید.

تغییر زمانبندی با Change

متد Change به شما اجازه می‌دهد تا مقادیر dueTime و period یک تایمر در حال اجرا را به‌روزرسانی کنید. این کار برای متوقف کردن موقت یا تغییر فاصله‌ی زمانی اجرای تایمر بسیار مفید است.

Copy Icon Program.cs
Timer myTimer = new(PrintTime, null, 0, 1000);

Console.WriteLine("Timer running every second for 5 seconds...");
Thread.Sleep(5000);

// Stop the timer from firing periodically by setting the period to infinite.
myTimer.Change(Timeout.Infinite, Timeout.Infinite);
Console.WriteLine("Timer stopped.");

// Clean up the timer resources.
myTimer.Dispose();

هشدار: تایمرها و مسائل همزمانی

یک نکته‌ی بسیار مهم که باید به خاطر داشته باشید این است که متد callback شما توسط نخ‌های Thread Pool اجرا می‌شود. این یعنی اگر فاصله‌ی زمانی (period) تایمر شما کمتر از مدت زمان اجرای خود متد callback باشد، ممکن است دو یا چند فراخوانی از callback شما به صورت همزمان روی نخ‌های مختلف اجرا شوند.

این وضعیت می‌تواند منجر به شرایط رقابتی (Race Conditions) شود، دقیقاً مانند مشکلی که در درس قبل با آن مواجه شدیم. بنابراین، اگر متد callback شما به داده‌های مشترکی دسترسی پیدا می‌کند، باید آن دسترسی را با استفاده از مکانیزم‌های همگام‌سازی مانند lock محافظت کنید.