مقدمه
در بسیاری از برنامهها، نیاز داریم تا یک عملیات خاص را نه به صورت مداوم، بلکه در فواصل زمانی
معین و به صورت دورهای اجرا کنیم. برای مثال، بررسی وضعیت یک سرویس هر دقیقه، ذخیرهی خودکار یک
سند هر پنج دقیقه، یا بهروزرسانی دادهها از یک منبع خارجی هر ساعت. ایجاد یک نخ (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 استفاده کنید.
ایجاد یک تایمر ساده
بیایید یک تایمر بسازیم که هر دو ثانیه، زمان فعلی را در کنسول چاپ کند.
Program.cs
using System.Threading;
static void PrintTime(object state)
{
Console.WriteLine($"Time is: {DateTime.Now.ToLongTimeString()}, State: {state}");
}
Console.WriteLine("Starting timer. Press Enter to exit.");
Timer myTimer = new Timer(PrintTime, "MyTimer", 0, 2000);
Console.ReadLine();
myTimer.Dispose();
در این مثال، متد PrintTime هر دو ثانیه یک بار توسط یک نخ از Thread Pool فراخوانی میشود. از
آنجایی که نخهای تایمر به صورت پسزمینه (background) هستند، باید با Console.ReadLine() نخ اصلی
را زنده نگه داریم تا برنامه بلافاصله بسته نشود.
مدیریت طول عمر تایمر
شما میتوانید رفتار یک تایمر را پس از ایجاد آن نیز تغییر دهید یا آن را متوقف کنید.
تغییر زمانبندی با Change
متد Change به شما اجازه میدهد تا مقادیر dueTime و period یک تایمر در حال اجرا را
بهروزرسانی کنید. این کار برای متوقف کردن موقت یا تغییر فاصلهی زمانی اجرای تایمر بسیار مفید
است.
Program.cs
Timer myTimer = new(PrintTime, null, 0, 1000);
Console.WriteLine("Timer running every second for 5 seconds...");
Thread.Sleep(5000);
myTimer.Change(Timeout.Infinite, Timeout.Infinite);
Console.WriteLine("Timer stopped.");
myTimer.Dispose();
هشدار: تایمرها و مسائل همزمانی
یک نکتهی بسیار مهم که باید به خاطر داشته باشید این است که متد callback شما توسط نخهای
Thread Pool اجرا میشود. این یعنی اگر فاصلهی زمانی (period) تایمر شما کمتر از
مدت زمان اجرای خود متد callback باشد، ممکن است دو یا چند فراخوانی از callback شما به صورت
همزمان روی نخهای مختلف اجرا شوند.
این وضعیت میتواند منجر به شرایط رقابتی (Race Conditions) شود، دقیقاً مانند
مشکلی که در درس قبل با آن مواجه شدیم. بنابراین، اگر متد callback شما به دادههای مشترکی دسترسی
پیدا میکند، باید آن دسترسی را با استفاده از مکانیزمهای همگامسازی مانند
lock محافظت کنید.