مدیریت Event Handlerهای غیرهمزمان (Async) در #C
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۳/۲۰ ۱۰:۵۲
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
async/await در زبان #C، به یکی از ارکان اصلی توسعه نرمافزارهای مدرن، بهویژه در پلتفرم .NET، تبدیل شده است. این قابلیت به ما اجازه میدهد تا برنامههایی پاسخگوتر (Responsive) و کارآمدتر بسازیم. با این حال، ادغام این الگوی جدید با برخی از ویژگیهای قدیمیتر زبان، مانند Event Handler ها، چالشهای منحصربهفردی را به وجود میآورد. این مقاله به بررسی عمیق مشکلات ناشی از Event Handlerهای غیرهمزمان میپردازد و راهکارهای عملی برای مدیریت ایمن آنها را ارائه میدهد.void است:void Button_Click(object sender, EventArgs e)
{
// کد مربوط به پردازش رویداد
}async مشخص کنیم. با توجه به اینکه امضای Event Handler باید حفظ شود، نوع بازگشتی void باقی میماند و متد ما به شکل async void درمیآید:async void Button_Click(object sender, EventArgs e)
{
// فراخوانی یک متد غیرهمزمان
await SomeAsyncTask();
}async و void نقطهای است که ویژگیهای زبان C# برای مدیریت عملیات غیرهمزمان، ما را با خطراتی جدی روبرو میکند.async voidخطرناک هستند؟async void استفاده نکنید." دلیل این توصیه بسیار مهم است. وقتی یک متد غیرهمزمان، به جای Task یا Task، مقدار void بازمیگرداند، فراخواننده هیچ راهی برای تعامل با عملیات غیرهمزمان آغاز شده ندارد.async Task به فراخواننده یک "وعده" یا شیء Task برمیگرداند. این شیء به فراخواننده اجازه میدهد تا:await منتظر اتمام عملیات بماند.try-catch، خطاهایی که در طول عملیات رخ میدهد را مدیریت کند.async void هیچکدام از این قابلیتها را ارائه نمیدهد. به محض فراخوانی، کنترل بلافاصله به فراخواننده بازمیگردد و عملیات غیرهمزمان به صورت "آتش کن و فراموش کن" (Fire and Forget) اجرا میشود. این عدم کنترل، بهویژه در مدیریت خطا، فاجعهبار است.async voidasync void را میدانیم، میتوانیم ریسکهای مشخصی که در Event Handlerهای غیرهمزمان وجود دارد را بررسی کنیم:async void رخ دهد و مدیریت نشود، این خطا از دسترس بلوک try-catch کد فراخواننده خارج است. در نتیجه، این استثنا مستقیماً به SynchronizationContext فعال در زمان شروع متد، پرتاب میشود. در بسیاری از اپلیکیشنهای UI، این اتفاق منجر به خاتمه یافتن ناگهانی برنامه (Crash) میشود، بدون آنکه بتوان آن را به شیوهای زیبا مدیریت کرد.async void ماند، هماهنگ کردن آن با سایر بخشهای برنامه دشوار میشود. اجرای همزمان چندین Event Handler یا تداخل آنها با سایر فرآیندها میتواند منجر به شرایط مسابقه (Race Conditions)، خرابی دادهها (Data Corruption) یا وضعیتهای نامعتبر در برنامه شود.async نیست و در تمام Event Handlerها وجود دارد. اگر فراموش کنید که یک Event Handler را پس از اتمام کار از رویداد مربوطه لغو اشتراک (Unsubscribe) کنید (با عملگر -=)، شیء حاوی Event Handler در حافظه باقی میماند و توسط Garbage Collector آزاد نمیشود که این امر منجر به نشت حافظه خواهد شد.async void هستیم، مسئولیت مدیریت خطرات آن کاملاً بر عهده توسعهدهنده است. خوشبختانه راهکارهایی برای کاهش این خطرات وجود دارد.try-catchtry-catch قرار گیرند. این کار تضمین میکند که هیچ استثنایی از مرز متد async void خارج نمیشود.async void SaveButton_Click(object sender, EventArgs e)
{
try
{
// تمام کدهای غیرهمزمان و همزمان در اینجا قرار میگیرند
await SaveDataToServerAsync();
UpdateUI("اطلاعات با موفقیت ذخیره شد.");
}
catch (Exception ex)
{
// مدیریت خطا: نمایش پیام به کاربر، لاگ کردن خطا و غیره
LogError(ex);
UpdateUI("خطا در ذخیرهسازی اطلاعات!");
}
}catch باید خطا را به شکل مناسبی مدیریت کنید؛ برای مثال، آن را لاگ کرده و یک پیام قابل فهم به کاربر نمایش دهید. هدف اصلی این است که از Crash کردن برنامه جلوگیری شود.CancellationTokenCancellationTokenSource با زمان انقضای مشخص ایجاد میکنیم و توکن آن را به متدهای غیرهمزمان داخلی پاس میدهیم.async void FetchDataButton_Click(object sender, EventArgs e)
{
// ایجاد یک CancellationTokenSource که پس از 10 ثانیه لغو میشود
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
{
try
{
// پاس دادن توکن به عملیات غیرهمزمان
var data = await FetchDataFromApiAsync(cts.Token);
DisplayData(data);
}
catch (OperationCanceledException)
{
// مدیریت حالت انصراف به دلیل اتمام زمان
UpdateUI("عملیات به دلیل طولانی شدن بیش از حد، لغو شد.");
}
catch (Exception ex)
{
// مدیریت سایر خطاها
LogError(ex);
UpdateUI("خطایی در دریافت اطلاعات رخ داد.");
}
}
}FetchDataFromApiAsync طوری طراحی شده باشد که CancellationToken را بپذیرد و به درخواست انصراف احترام بگذارد. مسئولیت پیادهسازی این منطق همچنان با توسعهدهنده است.async void در #C با آن مواجه میشویم؟async با امضای void که توسط ساختار رویدادها تحمیل میشود، ما را در برابر خطرات جدی، بهویژه استثناهای کنترلنشده، آسیبپذیر میکند. کلید استفاده ایمن از آنها، پذیرش مسئولیت و اجرای دقیق الگوهای دفاعی است. با قرار دادن تمام منطق درون یک بلوک try-catch و استفاده هوشمندانه از CancellationToken برای عملیات طولانی، میتوانیم از مزایای پاسخگویی برنامههای غیرهمزمان بهرهمند شویم، بدون آنکه پایداری نرمافزار خود را به خطر بیندازیم.async void، ناتوانی کد فراخواننده در گرفتن (catch) استثناهایی است که از آن متد پرتاب میشوند. این استثناها به صورت مشاهدهنشده باقی مانده و اغلب باعث از کار افتادن (Crash) کامل اپلیکیشن میشوند.