قبل شروع یک rollback بزنیم به Synchronous programming .
در این نوع programming برنامه Sequential اجرا میشه یعنی ؟
نرم افزار بر روی یک thread اجرا شده و clr شروع میکند خط به خط کد ها را اجرا کردن و اگر خط قبلی اجرا نشود هیچوقت خط بعدی اجرا نخواهد شد .
اشکال : اگر اجرای یک خط کد ۱۰ ثانیه زمان ببرد ، چون عملیات فوق (عملیات ۱۰ ثانیه ) بر روی thread اصلی نرم افزار ( main thread ) اجرا شده است ، نرم افزار تا اتمام آن خط در همان حالت و وضعیت منتظر میماند تا ۱۰ ثانیه به پایان برسد بعد وارد خط بعدی شود .
مثلا فرض کنید اگر ما یک عملیات زمانبری داشته باشیم مثل گذارش گیری ماهانه یا قرار باشد همزمان علاوه بر گذارش گیری چند عملیات دیگر هم انجام دهیم ، در این شرایط انجام عملیات همزمان ( parallel ) غیر ممکن است چون عملیات گذارشگیری بر روی main thread نرم افزار اجرا شده ، در این شرایط نه تنها عملیات دیگری نمیتواند اجرا شود بلکه کل نرم افزار صبر میکند تا عملیات اول ( گذارشگیری ) به اتمام برسد بعد وارد عملیات بعدی شود .
ما نیاز داریم نرم افزار ، همزمان با گذارشگیری ، عملیات دیگری هم اجرا کند . اینجاست که باید برویم سراغ Asynchronous programming :
در Asynchronous programming نرم افزار ما در شرایط ذکر شده صبر نمیکند و میتواند همزمان با گذارش گیری ، عملیات های دیگری هم اجرا کند .
سوال : چگونه این اتفاق شدنیست ؟
این سوال مربوط به multi threading است ولی در این متن تصميم ندارم وارد مباحثش شوم ولی توضیح مختصری ازش میگویم که گوشیه زهنمان باشد .
سی پی یو cpu چند هسته ای هست و هر هسته از چندین thread تشکیل شده که ما میتونیم برای این که سرعت و کارایی نرم افزارمان را بالا ببریم و از تمام قدرت cpu برای اجرای نرم افزارمان استفاده کنیم .
چون یک thread نمیتواند بیشتر از یک process را هندل کند ، میتوانیم جدا از main thread ، از thread های دیگر cpu به طور مستقل از هم و همزمان استفاده کنیم .
نکته : در حالت multi threading نباید چند عملیات که همزمان اجرا میشوند به هم وابسته باشند چون اگر اجرا شدن متدی ، وابسته به نتیجه ( return ) متد دیگر باشد ، خطا رخ میدهد و برای این نوع عملیات حتما باید صبر کرد .
کاربرد اصلی Asynchronous این است که نرم افزار منتظر نمیماند به این شکل که main thread نرم افزار را آزاد میکنیم و برای هر عملیات یک thread دیگر و مستقل تخصیص میدهیم .
دو کلمه کلیدی Async awayt :
Async :
این کلمه کلیدی در ابتدای متد اعلام میکرد که متد جاری یک متد از نوع Asynchronous است .
توجه : در اتمام نام متد های Asynchronous حتما باید با Async پایان یابد که زمان خوانده نام متد ، مشخص شود این متد یک متد Asynchronous است .
مثل نام متد زیر :
GetCarNameAsync();
وقتی متدی از نوع Async تعریف می شود باید از یک سری قوانین تبعیت کند :
متدی که از نوع Async است حتما باید یک Task را اجرا کند و خروجی از نوع Task برگرداند که میتواند یک Task از نوع T باشد .
به شکل زیر :
Task
Task<T> ==> Task<int> , Task<string>
void
یک task میتواند اجرای یک یا چند thread باشد که وقتی یک متد async اجرا میشود در واقع clr آن را در یک thread مستقل و همزمان با thread های دیگر و در background اجرا میکند .
نکته: توجه داشته باشید که یک thread نمیتواند مقدار بازگشتی داشته باشد ولی Task میتوان و یک thread میتواند فقط یک process رو هندل کند ولی Task چندیدن کار را میتوند انجام دهد .
البته همیشه و حتما معنای task در ابتدای متد ها به معنای run شدن آن متد در یک thread جدید نیست که جلوتر توضیحات کامل ارائه میشود .
Await :
اگر متدی در یک thread مستقل اجرا شود در واقع ، وقتی کامپایلر به بلاک آخر متد برسد آن thread بسته میشود و هیچ وقت از وضعیات عملیاش و در صورت بروز هر گونه خطا در زمان اجرا باخبر نخواهیم شد پس حتما برای متد های Async task صبر کنیم مگر در شرایط خواص .
برای این کارمیتوانیم از کلمه کلیدی await استفاده کنیم .
این کلمه کلیدی این امکان را میدهد که ما در thread اجرا شده صبر کنیم و تا به پایان نرسیدن عملیات ، thread بسته نشود .
به مثال زیر توجه کنید :
static async Task Main(string[] args)
{
var threadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Main:" + threadId);
await GettaskAsync();
threadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Main:" + threadId);
Console.ReadKey();
}
public static async Task GetTaskAsync()
{
var threadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("GettaskAsync:" + threadId);
await Task.Delay(1000);
threadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("GettaskAsync:" + threadId);
}
شرح کد : متدی از نوع async به نام Main بر روی thread اصلی نرم افزارمان اجرا است ، وقتی کامپایلر بر روی await داخل متد Main میرسد ، با همان thread جاری وارد متد async فراخوان شده ( GetTaskAsync ) میشود و شروع به پردازش متد دوم میکند ، حال اگر در بدن متد فراخوان شده await داشته باشیم و به آن برسد ، در صورت این که پردازش آن Await زمان بر باشد و باعث معلق شدن نرم افزار یا thread اصلی شود ، در این لحضه thread اصلی ( جاری ) را آزاد کرده و یک thread جدید اجرا میکند و به صورت yield تا پایان عملیات به متد فراخوانی شده ما گوش میدهد و در زمان پایان عملیات thread جدید ، ادامه عملیات خود را با همان thread جدید ساخته شده ادامه داده و به متد اول باز می گردد ، که این کار یک روش . net ، برای جلوگیری از thread deadlock است .
حال اگر در متد دوم ( thread دوم ) هم یک await باشد ، عملیات بالا را تکرار میکند .
در این شرایط نرم افزار هنگ نمیکند چون این روش یک multi tasking بود و در دات نت ، async یک signature برای پیاده سازی multi tasking است .
قطعا برنامه نویسی muti threading پیچیده است به همین دلیل .net با روش TAP این امکان را به ما میدهد که با استفاده از کلمات کلیدی async/await/Task کد های مشابه sync بنویسیم و کامپایلر زحمت تغییرش به multi threading برایمان بکشد .
قوانین استفاده از async/await/Task :
یک بار async و همواره async :
• هیچوقت یک متد async را در یک متد sync فراخوان نکنید . برای متد های sync متد معادل sync توسعه دهید و برای متد های async متد های معادل async توسعه دهید و استفاده کنید .
• هیچوقت از دستور .result و یا .wait استفاده نکنیند چرا که این کار باعث بروز deadlock شود .
به کد زیر توجه کنید :
public string GetCarAsync()
{
return GetCarNameAsync():
}
public async Task<string> GetCarNameAsync()
{
await Task.Run(() => Task.Delay(1000));
return "praid";
}
شرح :امکان return نتیجه متد async در یک متد sync وجود ندارد چون متد async یک Task برمیگرداند .
متن خطای کد بالا "امکان تغییر string یه یک Task<straing> به روش implicit وجود ندارد" .
پس برای این که بتوانیم صورت مسئله را پاک کنیم ، از دستور .result در انتهای فراخوانی اضافه میکنیم تا مقدار result (که مقدار string باشد) را از Task گرفته ( جداکرده ) و نتیجه یک string بازگرداند .
return GetCarNameAsync().result;
علت خطا : این کار باعث اجرا شدن عملیات async به روش sync میشود ولی همواره دو thread درگیر هندل کردن یک عملیات sync هستند ، در این شرایط دات نت thread را lock کرده و deadlock رخ میدهد .
پیشنهاد : استفاده از GetAwaitr().GetResult(); به جای دیتور .result.
در شرایطی که ناچار به استفاده از متد async در متد sync شدید ، به جای دستور .result میتوانید از دستور زیر برای این کار استفاده کنید
return GetCarNameAsync().GetAwaitr().GetResult();
این دستور اجازه میدهد زمان بروز exception در thread ، به جای throw کردن aggregate exception ، میتواند exception را برگرداند .
• دستور Task.Run .
متد Run به عنوان ورودی یک Delegate از نوع Action میگیرد و بلافاصله یک thread اجرا کرده و delegate دریافت کرده را در thread جدید اجرا می کند . پس به جز مواقع نیاز و ضروری ، از این دستور استفاده نکنیم چون یک thread از thread pool را اشغال میکند .
نکته مهم : در صورت لزوم میتوانید ، به جای Task.Run از دستور Task.FromResult استفاده کنید ، با همان عملکرد و با این تفاوت که thread جدید اجرا نمیکند .
توجه : ما به جای delegate action از lambda expression استفاده کرده ایم .
به متد های زیر توجه کنید :
public async Task<string> GetCarAsync()
{
return await GetCarNameAsync();
}
public async Task<string> GetCarNameAsync()
{
await Task.Run(() => Task.Delay(1000));
return "praid";
}
نکته : اگر برای دستور Task.Run مانند دستور بالا await استفاده کنید یعنی برایش صبر کنید ( مانند کد بالا) ، clr برای اجرا شدن آن thread ، در همان line صبر میکند و بعد از پایان عملیات به خط بعدی میرود ولی اگر await نداشته باشد ، thread جدید ساخته و همزمان با thread اصلی در background اجرا میشود ، ولی با این تفاوت که clr منتظر پایان این thread نمیماند و بعد از اجرای خط Task.Run به خط بعدی میرود و همزمان thread جدید در backgroun اجرا است .
توجه : اگر متد خروجی نداشته باید فقط Task برمیگرداند پس به ۳ دلیل هیچگاه از دستور async Task void استفاده نکنید .
1 - در متد های async دیگر نمیتوانیم فراخوانی و await کنیم ، چون خروجی آن از نوع void است نه Task ، پس حتما نتیجه بازگشتی آن با Task متفاوت است و احتمال بروز dead lock در عملیات آن وجود دارد .
نکته : استفاده از این روش در شرایط خیلی خواص و با دانش و اگاهی کامل از عملیاتی که در حال توسعه و پیاده سازی ان هستیم بلامانع است .
2- در متد async Task void اگر exception رخ دهد ، نمیتوانید آن را throw catch کنید چون نتیجه متد از نوع void است و مقداری بر نمیگرداند .
دوستان عزیز ، خیلی ممنون که وقت گران قدرتونو گذاشتید و تا به اینجا همراهیم کردید .
اگه اشتباه مفهومی داشتم یا ایراد تایپی داشتم یا ایراد توضیح داشتم خیلی خوشحال میشم بهم بگید تا اصلاح کنم .
مخلصیم ...