درک Task.Delay در برنامهنویسی ناهمگام #C
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۳/۰۳ ۱۱:۱۸
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
Task.Delay است. این متد به برنامهنویسان امکان میدهد تا اجرای کد را برای مدت زمان مشخصی به تأخیر بیندازند، بدون اینکه رشته (thread) فعلی را مسدود (block) کنند. این ویژگی کلیدی، Task.Delay را به ابزاری ضروری در توسعه برنامههای واکنشگرا (responsive) و کارآمد تبدیل میکند. در این مقاله، به بررسی عمیق Task.Delay، تفاوت آن با Thread.Sleep، کاربردهای رایج، اشتباهات متداول، و نکات پیشرفته آن خواهیم پرداخت.Task.Delayچیست و چرا باید از آن استفاده کرد؟Task.Delay یک متد در چارچوب کاری Task Parallel Library (TPL) سیشارپ است که یک Task را ایجاد میکند که پس از یک بازه زمانی مشخص به اتمام میرسد. برخلاف Thread.Sleep که رشته جاری را برای مدت زمان مشخصی متوقف میکند و باعث میشود برنامه در آن بازه زمانی هیچ کاری انجام ندهد، Task.Delay رشته را مسدود نمیکند. در عوض، کنترل را به فراخواننده (caller) بازمیگرداند و به سایر عملیات ناهمگام اجازه میدهد تا در پسزمینه اجرا شوند. این رفتار غیرمسدودکننده (non-blocking)، برای حفظ پاسخگویی رابط کاربری (UI responsiveness) و جلوگیری از توقف کامل برنامه بسیار حیاتی است.// مسدودکننده - در کد ناهمگام از این استفاده نکنید! Thread.Sleep(1000); // رشته را برای 1 ثانیه مسدود می کند // غیرمسدودکننده - روش صحیح در کد ناهمگام await Task.Delay(1000); // بدون مسدود کردن، 1 ثانیه صبر می کند
Task.DelayTask.Delay، پیادهسازی محدودیت نرخ در فراخوانیهای API است. این کار از ارسال بیش از حد درخواستها در یک بازه زمانی کوتاه جلوگیری میکند و به شما امکان میدهد تا از محدودیتهای سرویسهای خارجی تبعیت کنید.public async Task<IEnumerable<Data>> FetchDataWithRateLimitAsync()
{
var results = new List<Data>();
foreach (var item in items)
{
// Fetch data
var data = await apiClient.GetDataAsync(item);
results.Add(data);
// Wait 1 second before next request
await Task.Delay(1000);
}
return results;
}Task.Delay برای پیادهسازی مکانیزمهای تلاش مجدد با تأخیر نمایی (exponential backoff) بسیار مفید است. این روش به برنامه اجازه میدهد تا در صورت بروز خطا در عملیات ناپایدار (flaky operations)، با فواصل زمانی افزایشی، عملیات را مجدداً امتحان کند و شانس موفقیت را افزایش دهد.public async Task<T> RetryWithBackoffAsync<T>(Func<Task<T>> operation, int maxRetries = 3)
{
for (int i = 0; i < maxRetries; i++)
{
try
{
return await operation();
}
catch (Exception) when (i < maxRetries - 1)
{
// Exponential backoff: 1s, 2s, 4s, ...
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i)));
}
}
// Last attempt
return await operation();
}RetryWithBackoffAsync عملیاتی را چندین بار امتحان میکند و در صورت شکست، قبل از تلاش مجدد، تأخیر را به صورت نمایی افزایش میدهد.Task.Delay میتواند به حفظ پاسخگویی برنامه کمک کند. با استفاده از آن، میتوان به رشته UI زمان داد تا بهروزرسانیها را پردازش کند، بدون اینکه رابط کاربری منجمد شود.public async Task UpdateProgressBarAsync()
{
for (int i = 0; i <= 100; i++)
{
progressBar.Value = i;
// Give UI thread time to update
await Task.Delay(50);
}
}Task.Delay(50) تضمین میکند که رابط کاربری فرصت داشته باشد تا مقدار جدید را نمایش دهد.Task.Delay همچنین برای پیادهسازی نظرسنجی ناهمگام با یک مهلت زمانی مشخص کاربرد دارد، که در آن برنامه به طور دورهای یک شرط را بررسی میکند تا زمانی که برآورده شود یا مهلت زمانی به پایان برسد.public async Task<bool> WaitForConditionAsync(Func<bool> condition, TimeSpan timeout)
{
var startTime = DateTime.UtcNow;
while (!condition())
{
if (DateTime.UtcNow - startTime > timeout)
return false;
await Task.Delay(100);
}
return true;
}WaitForConditionAsync به طور مداوم یک شرط را بررسی میکند و در صورت عدم برآورده شدن شرط در مهلت زمانی مشخص، false را برمیگرداند.Task.DelayTask.Delay میتواند به همان اندازه استفاده از Thread.Sleep مشکلساز باشد. درک اشتباهات رایج به شما کمک میکند تا از مشکلات احتمالی جلوگیری کنید.Thread.Sleep به جای Task.Delay: همانطور که قبلاً ذکر شد، استفاده از Thread.Sleep در کدهای ناهمگام منجر به مسدود شدن رشته و از دست دادن پاسخگویی برنامه میشود.// ❌ بد: رشته را مسدود می کند
public async Task BadMethodAsync()
{
Thread.Sleep(1000); // رشته را مسدود می کند
await DoSomethingAsync();
}
// ✅ خوب: تأخیر غیرمسدودکننده
public async Task GoodMethodAsync()
{
await Task.Delay(1000); // مسدود نمی کند
await DoSomethingAsync();
}await: یکی دیگر از اشتباهات رایج، فراخوانی Task.Delay بدون استفاده از کلمه کلیدی await است. در این صورت، Task.Delay یک Task ایجاد میکند اما برنامه منتظر اتمام آن نمیماند و بلافاصله به اجرای خطوط بعدی کد میپردازد.// ❌ بد: فراموش کرده اید await کنید
public async Task BadMethodAsync()
{
Task.Delay(1000); // Task را ایجاد می کند اما منتظر آن نمی ماند
Console.WriteLine("This prints immediately!");
}
// ✅ خوب: به درستی await شده است
public async Task GoodMethodAsync()
{
await Task.Delay(1000);
Console.WriteLine("This prints after 1 second!");
}Task.Delay از توکنهای لغو (cancellation tokens) پشتیبانی میکند، که آن را برای سناریوهای مهلت زمانی ایدهآل میسازد. این قابلیت به شما اجازه میدهد تا یک عملیات Delay را قبل از اتمام زمان مقرر، لغو کنید.public async Task<bool> TryOperationWithTimeoutAsync(
Func<Task> operation,
TimeSpan timeout)
{
using var cts = new CancellationTokenSource(timeout);
try
{
await Task.WhenAny(
operation(),
Task.Delay(timeout, cts.Token)
);
return true;
}
catch (OperationCanceledException)
{
return false;
}
}Task.WhenAny منتظر میماند تا یا operation() کامل شود یا Task.Delay به دلیل مهلت زمانی یا لغو توسط CancellationTokenSource به پایان برسد.public static class DelayExtensions
{
public static async Task DelayWithProgressAsync(
TimeSpan duration,
IProgress<double> progress = null)
{
var startTime = DateTime.UtcNow;
var endTime = startTime + duration;
while (DateTime.UtcNow < endTime)
{
var elapsed = DateTime.UtcNow - startTime;
var percentage = elapsed.TotalMilliseconds / duration.TotalMilliseconds;
progress?.Report(percentage);
await Task.Delay(100);
}
progress?.Report(1.0);
}
}DelayWithProgressAsync اجازه میدهد تا پیشرفت تأخیر را به یک شیء IProgress گزارش دهید.Task: هر فراخوانی Task.Delay یک شیء Task جدید ایجاد میکند. بنابراین، از استفاده از آن در حلقههای بسیار فشرده (tight loops) که عملکرد حیاتی است، خودداری کنید.Task.Delay تضمین نمیکند که دقیقا پس از زمان مشخص شده کامل شود. این یک حداقل تأخیر (minimum delay) است و ممکن است به دلیل بار سیستم یا زمانبندی رشتهها، کمی بیشتر طول بکشد.Thread.Sleep در موارد خاص (جایی که مسدود شدن رشته قابل قبول است و در یک رشته پسزمینه اتفاق میافتد) کارآمدتر باشد، هرچند این موارد نادر هستند.await استفاده کنید: همانطور که اشاره شد، عدم استفاده از await باعث میشود که Task.Delay به صورت غیرمنتظرهای عمل کند.TimeSpan: به جای استفاده از میلیثانیه به عنوان یک عدد صحیح، استفاده از TimeSpan برای تعیین مدت زمان تأخیر، کد را خواناتر و کمتر مستعد خطا میکند.CancellationToken استفاده کنید.Task.Delay(0) اجتناب کنید:Task.Delay(0) به این معنی است که Task بلافاصله پس از زمانبندی، تکمیل میشود. این معمولاً نشانهای از "بوی کد" (code smell) است و در بیشتر موارد میتوان به جای آن از Task.Yield() یا صرفاً ادامه اجرای ناهمگام استفاده کرد تا کنترل را به زمانبند بازگرداند.Task.Delay را در سناریوهای با فرکانس بالا در نظر بگیرید و در صورت لزوم، راهکارهای جایگزین را بررسی کنید.Task.Delay ابزاری قدرتمند و انعطافپذیر در جعبه ابزار برنامهنویسی ناهمگام #C است. با درک صحیح عملکرد غیرمسدودکننده آن و تفاوتهایش با Thread.Sleep، توسعهدهندگان میتوانند برنامههایی بسازند که هم واکنشگرا باشند و هم کارایی بالایی داشته باشند. از پیادهسازی محدودیت نرخ و منطق تلاش مجدد گرفته تا بهبود پاسخگویی رابط کاربری، Task.Delay کاربردهای گستردهای دارد. با رعایت بهترین روشها و آگاهی از اشتباهات رایج، میتوانید از این متد به طور موثر برای ساخت برنامههای داتنت پیشرفته و مقاوم استفاده کنید و کنترل بینظیری بر جریان اجرای کد ناهمگام خود داشته باشید.