مروری بر Asynchronous Programming در دات نت

آخرین بروز رسانی: 1403/07/02

در c# 5 ویژگی معرفی شد به اسم برنامه نویسی همزمانی (asynchronous programming). در این مقاله میخایم مختصری توضیح پایه درباره سینتکس کدنویسی آن در دات نت رو مروری بکنیم. بعد میریم سراغ نکاتی که باید در این مورد بدونیم. 
همین ابتدا بگم این مقاله بیشتر بدرد developerهای junior و midd میخوره. ولی نکاتی گفته میشه که بعضی وقتها برنامه نویستهای senior هم رعایت نمیکنن یا براشون میتونه جدید باشه.

خوب بریم شروع کنیم


خلاصه و کوتاه بگم هر وقت ما در برنامه عملیات i/o داشتیم باید از async programming استفاده کنیم
i/o ها چیها هستن؟     database -  file   - network
بریم یه مثال ساده ببینیم (البته در این مثال i/o نداریم فقط میخام سینتکس رو بگم)

async Task<bool> Func1()
{
        Console.WriteLine("Started");
        await Task.Delay(5000); 
        Console.WriteLine("Completed");
        return true;
}


دقت کنید ما از modifier async  برای تعریف تابع استفاده کردیم و همچنین خروجی تابع هم از نوع <Task<T هست
نحوه کال کردن این تابع چه فرقی با توابع معمولی داره؟ 

var res=await  Func1();


باید از oprand await قبل از اسم تابع استفاده کنید و تمام.


در مثال بالا به

 await Task.Delay(5000); 

دقت کنید. خود کلاس task یه تایع استاتیک به اسم delay داره که اجرای ادامه کدهای برنامه رو به میزان ورودی به تعویق میندازه . دقت کنید موقع کال کردن خودش هم از await استفاده شده.

یه راه دیگه هم هست که میشه توابع async رو کال کرد  که اصلن فکر خوبی نیست. 👎

var res=Func1.Result;

تنها دلیلی که این مورد رو در ان ویدیو اوردم اینه که بگم اصلن این کار رو نکنید. این در اصل میاد و تابع رو بصورت سینک اجرا میکنه و برای زمانی در task  گذاشته شده بود که یکسری ویژیگها مثل eventها از متد sync پشتیبانی نمیکردن.

CancellationToken:

یکی از ویژیگهای جذاب task ها قابلیت کنسل کردنشونه. به این ترتیب اجرا اونها تا جایی که قبلش پیش رفته بود متوقف میشه و دیگه ادامش اجرا نمیشه واجرای برنامه  برمیگده به ترد اصلی برنامه
برای زمانهایی که مثلن شما یه web api نوشتی و کاربر درست بعد از کال کردن  api   و قبل از دریافت response  به هر دلیل پشیمون شده یا اصن بروزر و بسته . خوب در این حالت اصن جایی یا کسی منتظر اجرا و جواب چیزی نیست. با این ویژگی ادامه  کدها اجرا نمیشه و  دیگه منابع سیستم در گیر ادامه اجرا نمیشه و ازاد میشه

 

Task.Run :

کلاس Task یه تابع استاتیک دیگه هم داره به ایم Run. این هم یکی از اول مواردیه که ترجیحا ازش استفاده نکنید. خوب اگه قرار استفده نکنیم برا چی هست؟

این زمانی کاربرد داره که شما کدتون از نوع async نیست بنا به دلایلی مجبورید تابعی از نوع async داشته باشید. مثلن از یه اینترفیسی ارثبری میکنید که تابعش async هست و شما هم مجبورید به همون شکل پیاده کنید.
یا زمانی که کاری از نوع I/O بوده که ورژن async نداشته (که بعید میدونم دیگه الان باشه) و میخاید به این شکل تبدیلش کنید به ورژن async

 

ConfigureAwait(false):

چیزی که در مورد taskها باید بدونید اینه که وقتی کارشون تموم میشه نتیجه برمیگرده به contextیی که اون رو کال کرده و سینکرونایزیشن بینشون اتفاق میفته ، که این مورد خودش زمانبر هست در حالی که در خیلی مواقع نیازی نیست. برای جلوگیری از این مورد میتونید از این تابع استفاده کنید.

 var re= await SaveFileAsync("asdf asfdasdfasd").ConfigureAwait(false);

 

خوب تاحالا با یه تسک سروکار داشتیم ولی اگر با لیستی ازشون سروکار داشته باشیم چه حالتهایی ممکه پیش بیاد؟

Task.WhenAny

اگر لیستی از taskها رو داشته باشید و قرار باشه هر زمان فقط یکی از اونها که تموم شد بعد یه اتفاقی بیفته. این متد لیستی از taskها رو به عنوان ورودی میگیره و خروجیش هم یه task هست.
نکته: خروجی taskها نیاز نیست از یک نوع باشند و میتواند متفاوت باشند

 var firstCompletedTask  =await Task.WhenAny([task1,task2,tsak3]);
   if (firstCompletedTask .IsCompletedSuccessfully)
  {
// اگر اولین تسک انجام شده exception داشته باشه این قسمت انجام نمیشه
  }
if (firstCompletedTask .IsCompleted)
 {
     //در هر حالتی به محض اینکه اولین task انجام بشه این قسمت انجام میشه حتی اگر exception درش اتفاق افتاده باشه
 }

Task.WhenAll

این مورد هم مانند بالاست با این تفاوت که هر زمان اجرای تمام taskها کامل بشه (حتی اگر بعضیاشون هم exception بدن) به خط بعدی میره

اگر از WhenAll استفاده بشه خروجی توابع async میتونه با هم متفاوت باشه. در این حالت اگر تابعی exception  داشته باشه تاثیری در اجرای بقیه نداره ولی در انتها exception بر میگردونه (اگر بیشتر از یکی exeption داشته باشن در نهایت فقط خطای اولی به عنوان exception برمیگرده)

 await Task.WhenAll([task1,tsak2]);

اما اگر هم خروجی داشته باشند و یخایم از <Task.Whenall<T استفاده کنید باید خروجی همشون از یک جنس باشه و در صورتی که یکیشون یا بیشتر exction داشته باشند هم مانند بالا اتفاق میفته

 var results = await Task.WhenAll<string>([task1,tsak2]);

 

برای مطالعه بیشتر Task.WaitAny و Task.WaitAll رو مطالعه کنید

DeadLock

چند سوال:

💡اگر ما تابعی از جنس async رو کال کنیم بدون اینکه قبلش await بزاریم ,چی میشه؟ کد مثل حالت معمولی و بصورت async اجرا میشه ولی چون احتمالن برنامه قبل از اجرای کد تموم شده (مثلن اگر api باشه request تموم شده و response هم برگشته در نتیجه همه منابع اختصاص داده شده از حافظه بیرون انداخته شده اند) تا اخر اجرا میشه یا اگر درش از سرویسی استفاده کرده باشی که به علت بسته شده ترد و هر چی که بهش ربط داره,  در نتیجه خطای null exception میده .

💡اگر ما تکه کدی شبیه کد زیر بنویسیم چی میشه؟ یعنی خود تابع رو به بریزیم داخل یه متغییر

var task1 = AsyncFunc();

ایندفعه هیچ اتفاقی نمیفته و کد اجرا نمیشه و برای اجرای کد باید کد زیر رو بنویسی

await task;

الان تازه اجرا میشه

 

نظر دهید

آدرس ایمیل شما منتشر نخواهد شد. فیلدهای الزامی علامت گذاری شده اند *