پیاده سازی Async Await
یکشنبه 6 مهر 1399هدف از نوشتن این مطلب، شناسایی مشکلات و اشتباهات رایجی است که هنگام پیاده سازی رخ می دهد، زیرا Async / Await یک بحث پیچیده است و حتی یک اشتباه کوچک یا پیاده سازی اشتباه منجر به عدم ثبات سیستم ها می شود. ایده این نیست که چرخ را دوباره اختراع کنیم، بلکه تصاحب بهترین شیوه ها و دستورالعمل های به اشتراک گذاشته شده ی جوامع بزرگتر است.
علاوه بر این برای ما مهم است که اصول را کاملاً رعایت کنیم و با احتیاط از این دستورالعمل ها پیروی کنیم. همه ما به عنوان یک تیم باید اطمینان حاصل کنیم که از این موارد به درستی استفاده می کنیم.
مباحث
● جلوگیری ازاستفاده کورکورانه از Async/Await درهرجا
● راهنمای عملکرد پسوند Async به وسیله Async
● استفاده از ()Task.FromResult درصورت وجود مقدار بازگشتی و استفاده از Task.CompletedTask() درصورت عدم وجود مقدار بازگشتی، درهنگام پیاده سازی Task اصلی
● بررسی لغو توکن، یک پارامترتکمیلی برای لغو task/request مفید خواهد بود
● به جای استفاده از .Result ، ازGetAwaiter().GetResult استفاده کنید. این کار تأثیر زیادی در مدیریت خطاها و خطاهای تجمعی دارد. سعی کنید از این سناریو اجتناب کنید. به دلیل بحث Synchronization، در یک موقعیت قفلشده قرارمی گیرد.
● وقتی به sync context نیاز ندارید از ConfigureAwaiter(false) استفاده کنید.
● ازreturn برای دستور using استفاده نکنید.
● ازEF نمی توان به طور موازی (parallel) استفاده کرد. await.Task.WhenAll یا WhenAny. هنگامی که ما از EF به وسیله یک پایگاه داده استفاده می کنیم ، امن نیستند. از parallel فقط در مواردی که دارای رشته ایمن هستند استفاده کنید.
مورد اول: متد Async در یک متد void فراخوانی می شود
استفاده نادرست
public static async Task FooAsync() { // some async code here... await Task.Delay(10000); } public void ThisWillNotWaitForAsyncCodeToComplete() { try { Console.WriteLine("Before : " + DateTime.Now.ToString()); FooAsync(); Console.WriteLine("After : " + DateTime.Now.ToString()); } catch (Exception ex) { //The below line will never be reached Console.WriteLine(ex.Message); } }
استفاده درست
public static async Task FooAsync() { await Task.Delay(10000); } public async Task ThisWillNotWaitForAsyncCodeToCompleteAsync() { Console.WriteLine("Before : " + DateTime.Now.ToString()); await FooAsync(); Console.WriteLine("After : " + DateTime.Now.ToString()); }
تغییراتی که باید با دقت به آنها توجه شود:
FooAsync() -1با استفاده از await فراخوانی می شود.
-2متد دارای async Task درsignature است.
نتیجه
-1حلقه Async / await به درستی دنبال شده
Parent thread -2 منتظر کامل شدن child thread خواهد بود درصورت انجام هرگونه دیتابیس یا عملیات رویداد، parent thread قبل ازترک کردن منتظر اتمام کار می ماند
-3میزان دقت در این کار بالا است
مورد دوم: کلمه کلیدی "async" استفاده نشده و شی "task" بازگردانده نمی شود.
استفاده نادرست
public static Task BarAsync() { // some async code here... return Task.Delay(10000); } public void ThisWillNotWaitForAsyncCodeToComplete() { try { Console.WriteLine("Before : " + DateTime.Now.ToString()); BarAsync(); Console.WriteLine("After : " + DateTime.Now.ToString()); } catch (Exception ex) { //The below line will never be reached Console.WriteLine(ex.Message); } }
استفاده درست
public static async Task BarAsync() { await Task.Delay(10000); } public async Task ThisWillNotWaitForAsyncCodeToCompleteAsyncAsync() { Console.WriteLine("Before : " + DateTime.Now.ToString()); await BarAsync(); Console.WriteLine("After : " + DateTime.Now.ToString()); }
تغییراتی که باید با دقت توجه شود:
FooAsync() -1 از async استفاده می کند و task را برنمی گرداند.
2- ThisWillNotWaitForAsyncCodeToCompleteAsync() با استفاده از await، FooAsync() را فراخوانی می کند.
نتیجه
1- حلقه Async / await به درستی دنبال شده
2- Parent thread منتظر کامل شدن child thread خواهد بود. درصورت انجام هرگونه دیتابیس یا عملیات رویداد، parent thread قبل ازترک کردن منتظر اتمام کارمی ماند.
3- میزان دقت در این کار بالا است
مورد سوم: متد به عنوان "async Task" علامت گذاری شده است اما هیچ متد async ای در داخل فراخوانی نمی شود.
استفاده نادرست
Public void Foo() {} Public void Bar() {} public async Task FakeAsyncMethod() { Foo(); Bar(); Return Task.CompletedTask; }
استفاده درست
Public void Foo() {} Public void Bar() {} public void FakeAsyncMethod() { Foo(); Bar(); }
تغییراتی که باید با دقت توجه شود:
1- حذف async task از ()FakeAsyncMethod
2- حذف دستور return task
نتیجه
1- به طور معمول متد sync را همانطور که انتظار میرود فراخوانی کنید.
2- میزان دقت در این کار بسیار مهم است.
مورد چهارم: async به جای استفاده ازawait، blocking call دارد
استفاده نادرست
public static async Task FooAsync() { await Task.Delay(10000); } public void ThisWillNotWaitForAsyncCodeToCompleteAsync() { Console.WriteLine("Before : " + DateTime.Now.ToString()); FooAsync().Result; Console.WriteLine("After : " + DateTime.Now.ToString()); }
استفاده درست
public static async Task FooAsync() { await Task.Delay(10000); } public void ThisWillNotWaitForAsyncCodeToCompleteAsync() { Console.WriteLine("Before : " + DateTime.Now.ToString()); await FooAsync(); Console.WriteLine("After : " + DateTime.Now.ToString()); }
تغییراتی که باید با دقت توجه شود:
1- اضافه کردن await در FooAync() برای دنبال کردن زنجیره مناسب async/await
2- استفاده از .Result مزیت async را از بین می برد.
نتیجه
1- متد Async را باید طبق توصیه و روش صحیح انجام آن، فراخوانی کنید.
2- میزان دقت این بخش مهم است
مورد پنجم: مسدود سازی متدasync با استفاده از .Wait
استفاده نادرست
public async Task FooAsync(string id) { … some more function code without any await operation inside… await Task.Delay(10000); } public void Bar() { console.writeline(“Hello world”); FooAsync().Wait(); }
استفاده درست
public async Task FooAsync(string id) { … some more function code without any await operation inside… await Task.Delay(10000); } public async Task BarAsync() { console.writeline(“Hello world”); await FooAsync(); }
تغییراتی که باید با دقت توجه شود:
1- از async awaitدر BarAsync() استفاده کنید تا الگوی درست async/await را دنبال کنید.
نتیجه
1- متدasync باید به روشی که باید انجام شود، فراخوانی شود به جای آنکه به زور به sync تبدیل شوند ورشته فعلی را مسدود کند.
2- میزان دقت این بخش مهم است
مورد ششم: ایجاد task برای متد sync و انتظار برای task
استفاده نادرست
public void SomeMethod1() { … some function code…. Var task = Task.Run(() => SomeMethod2); task.Wait();… some functional code…. } public void SomeMethod2() { … Some function code goes here... }
استفاده درست
public void SomeMethod1() { … some function code…. SomeMethod2();… some functional code…. } public void SomeMethod2() { … Some function code goes here... }
تغییراتی که باید با دقت توجه شود:
به طور معمول از متد sync به روشی که مورد انتظار است استفاده کنید. ساختن یک task و انتظار برای task فقط هدر دادن یک thread اضافی در pool است که همان کار را می توان در خود thread اصلی انجام داد.
نتیجه
1- عملکرد بهینه شده برای ایجاد task وقتی متدی باید منتظر بماند.
2- میزان دقت این بخش مهم است.
مورد هفتم: بازیافتن نتیجه چندین task
استفاده نادرست
public async Task < string > FooAsync() { string result = string.empty;… some function code… return result; } public async Task < string > BarAsync() { string result = string.empty;… some function code… return result; }
public void ParentMethod() { var task1 = FooAsync(); var task2 = BarAsync(); Task.WaitAll(task1, task2); }
استفاده درست
public async Task < string > FooAsync() { string result = string.empty;… some function code… return result; }
public async Task < string > BarAsync() { string result = string.empty;… some function code… return result; } public async Task ParentMethod() { var task1 = FooAsync(); var task2 = BarAsync(); await task.WhenAll(task1, task2); }
تغییراتی که باید با دقت انجام شود:
ما باید از میکس شدن blocking & unblocking کد جلوگیری کنیم. Task.WaitAll یک نام گذاری blocking است در حالی که Task.WhenAll، nonblockin است و معنای async را حفظ می کند.
نتیجه
1- کد بهینه شده است و طبق دستورالعمل های برنامه نویسی async / await برای جلوگیری از مسدود شدن نامگذاری ها کار می کند.
2- میزان دقت این بخش بسیار مهم است
مورد هشتم: ورژن Sync زمانی استفاده می شود که Async در دسترس باشد.
استفاده نادرست
public bool CheckLabelAlreadyExist(string labelName, Guid facilityKey, int labelTypeCode) { return GetQueryable().Any(x => x.DescriptionText == labelName && x.FacilityKey == facilityKey && x.LabelTypeCode == labelTypeCode); }
استفاده درست
public async Task < bool > CheckLabelAlreadyExist(string labelName, Guid facilityKey, int labelTypeCode) { return await GetQueryable().AnyAsync(x => x.DescriptionText == labelName && x.FacilityKey == facilityKey && x.LabelTypeCode == labelTypeCode); }
قوانین نامگذاری متد:
1- همه متدهای async باید دارای پسوند "async" در نام متد باشند تا خوانایی آسان شود و تمایز بین متدهای sync و async وجود داشته باشد.
2- داشتن "async" در متدها، آن را آشکار کرده و احتمال خطا در پیاده سازی را کاهش می دهد.
نتیجه گیری
async / wait بهترین کار برای task های محدود به IO (ارتباطات شبکه ای ، ارتباطات پایگاه داده ، درخواست http و غیره) است. اما خوب نیست که درtask های محاسباتی اعمال شود (پیمایش لیست بزرگ ، پردازش یک تصویر بزرگ و غیره). زیرا thread نگهدارنده را از thread pool آزاد می کند و CPU/coreهای دردسترس، برای پردازش آن در task ها به کارگرفته نمی شوند. بنابراین ، باید از استفاده Async / Await برای task های محاسباتی جلوگیری شود.
برای پرداختن به taskهای محاسباتی، بهتر است ازTask.Factory.CreateNew با TaskCreationOptions که LongRunning است، استفاده کنیم. با استفاده از این روش تا زمان انجام task، یک background thread جدید برای پردازش یک taskسنگین محاسباتی بدون آزاد کردن مجدد آن ازthread pool، شروع می شود.
- C#.net
- 3k بازدید
- 7 تشکر