Asynchronous programming

قبل شروع یک 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 است و مقداری بر نمیگرداند .دوستان عزیز ، خیلی ممنون که وقت گران قدرتونو گذاشتید و تا به اینجا همراهیم کردید .اگه اشتباه مفهومی داشتم یا ایراد تایپی داشتم یا ایراد توضیح داشتم خیلی خوشحال میشم بهم بگید تا اصلاح کنم .مخلصیم ...