در این پست ، فرایند refactoring یک برنامه وب همزمان(synchronous) ASP.NET را به یک برنامه غیر همزمان (asynchronous) شروع می کنیم و نشان می دهیم چه نوع مشکلی ممکن است درانجام این کار بوجود آید.
برنامه وب که می خواهیم آنرا refactor کنیم یک برنامه نسبتاً ساده است. این سیستم یک سرویس خارجی را فراخوانی می کند تا داده های برگشتی توسط این سرویس را نمایش دهد.
داده هایی که بازگشت داده می شود مربوط به یکی از چهار objects زیر است:
Users, Posts, Albums, Photos
اگر بخش اول این مطلب رو مطالعه کرده باشید شما می دانید که برای اینکه یک برنامه synchronous را asynchronous کنیم و همه چیز را خراب نکنیم ، به طور کلی می خواهیم از پایین ترین سطح سلسله مراتب داده شروع کنیم که در اینجا مقصود object تصویر می باشد.
بنابراین ، بدون هیچ حرف بیشتری ، بیایید شی فوق رو refactor کنیم!
کد فوق مربوط است برای PhotoRepository :
من میخواهم تا آنجا که ممکن است کمترین کار ممکن را انجام دهم، بنابراین بلوک های کد را با استفاده از متد Task.Run به صورت asynchronous میکنم. من تصمیم گرفتم که فقط این کار را انجام دهم:
نکته جالب در مورد این راه حل این است که من حتی نیازی به تغییر اینترفیس IPhotoRepository نداشتم ، بنابراین کار بسیار کمی انجام شد. و اگر کد را اجرا کنیم ، می بینیم که به طرز شگفت انگیزی ، در واقع کار می کند!
آنچه ما انجام داده ایم یک ضد الگویی است که به عنوان async-over-sync شناخته شده است
هنگام اجرای این متد:
سیستمهای ما اکنون قادر به رسیدگی به درخواستهای همزمان کمتری نسبت به یک سیستم synchronous می باشد.
بنابراین اکنون این سؤال مطرح می شود: چگونه این مسئله را برطرف کنیم؟ برای پاسخ به آن ، باید دو مشکل را حل کنیم.
اولین مشکل کلاس WebClient است. یک جانشین جدید و مناسب تر برای فراخوانی API هایی که ما انجام می دهیم کلاس HttpClient است. من به عنوان مالک پروژه می خواهم کل پروژه خود را از استفاده از WebClient به HttpClient تغییر دهم. با این حال، زمانی که سعی میکنم این کار را انجام دهم، فورا به یک مشکل میرسم:
HttpClient does not expose synchronous methods!
مشکل دوم این است که ، همانطور که در بالا دیدیم ، wrap کردن کد synchronous در Task ایده بدی است. بنابراین در حال حاضر من "مجبور" شده ام تا دوباره refactor به سمت برنامهنویسی asynchronous انجام دهم.
بنابراین در اینجا کاری که می خواهم انجام دهم: من فقط قصد دارم تا Repository را به asynchronous تبدیل(refactor) کنم و نه PhotoController که درسطح بالاترقرار دارد.
به استفاده از .Result توجه کنید. معنی این امر این است که وقتی کنترلر repository را صدا می کند ، منتظر می ماند تا نتیجه متد برگردد ، سپس از آن نتیجه استفاده می کند. اما آیا واقعاً این اتفاق می افتد؟
نه. آنچه واقعاً اتفاق می افتد بسیار بدتر است ، و کاملاً واضح نیست.
به دلایلی که احتمالاً در حال حاضر مشخص نیست ، کد بالا منجر به deadlock خواهد شد. برای فهمیدن دلیل آن ، ابتدا باید چیزی را بدانیم که در هنگام فراخوان یک asynchronous task در پشت صحنه اتفاق می افتد.
هر زمان که یک asynchronous task ایجاد شود ، اگر thread اجرای آن کار باید منتظر بماند تا اتفاقی بیفتد ، میتواند محیط اجرایی را ترک کند و کار دیگری انجام دهد
اما قبل از این که آن را ترک کند، چیزی به نام SynchronizationContext ایجاد میکند، که context پیرامون اجرای task را ذخیره میکند و برای از سرگیری اجرای این برنامه در آینده مورد نیاز است.
توجه: پاراگراف فوق فقط مربوط به ASP.NET Framework است ، نه ASP.NET Core.
میدانم که این تصویر خیلی شلوغ و گیجکننده است، اما خوشبختانه یک دقیقه طول میکشد. برای توضیح دادن که چرا deadlock در حال رخ دادن است:
به عبارتی، کدهای async منتظر پایان کار Result هستند تا نتیجه را بازگردانند. در همین لحظه کدهای همزمان برنامه نیز منتظر کدهای async هستند تا خاتمه یابند. نتیجهی کار یک deadlock است.
بنابراین ما در این باره چه کار کنیم؟ شما ممکن است از پست قبلی به خاطر داشته باشید که async تمایل دارد مثل یک ویروس پخش شود. راهحل در اینجا این است که اجازه دهید این کار را انجام دهد وکنترلر را refactor کنید تا از async / await استفاده کند.
مخزن سورس پروژه فوق و همچنین مطالب بررسی شده در مقاله فوق در این لینک قابل دسترس می باشد