برنامه نویسی ناهمگام با async ، await و Task در سی شارپ
#C با استفاده از برخی توابع اصلی، کلاس ها و کلمات کلیدی رزرو شده از برنامه نویسی غیرهمزمان (asynchronous programming) پشتیبانی می کنند.
در دنیای امروز برنامه نویسی ناهمگام (asynchronous) در میان توسعه دهندگان بسیار محبوب شده است. وقتی ما با Ui سروکار داریم و یک متد که زمان اجرای آن طولانی است ( مثل خواندن یک فایل بزرگ و ذخیره آن در پایگاه داده) را در رویداد کلیکِ یک دکمه می گذاریم زمانی که روی آن دکمه کلیک شود رابط کاربری اپلیکیشن قفل شده و به اصطلاح هنگ می کند ، زیرا رابط کاربری و بقیه متد ها در برنامه نویسی همگام (Synchronous) در یک نخ (Thread) از CPU اجرا می شوند پس رابط کاربری تا زمانی که فعالیت متد پایان نیابد پاسخی به کاربر نمی دهد. برنامه نویسی ناهمگام در این شرایط بسیار کار آمد است ، زیرا در این روش رابط کاربری و متد ها به هم متکی نبوده و متد ها به صورت جداگانه اجرا می شوند.
به طور کلی با استفاده از برنامه نویسی ناهمگام می توانید میزان واکنش گرایی و سرعت نرم افزار های خود را به میزان قابل توجهی افزایش دهید. تکنیک های برنامه نویسی همگام و سنتی باعث می شود عملکرد نرم افزار پیچیده تر شود و این موضوع اشکال زدایی آن را سخت تر می کند.
کلمات کلیدی async و await از نسخه 5 زبان سی شارپ اضافه شده اند. این ویژگی ها در پاییز 2013 به عنوان بخشی از Visual Studio 2013 به بازار وارد شده اند و می توانند تا میزان زیادی، برنامه نویسی غیز همزمان را ساده تر کنند.
چرا به Async و Await نیاز داریم؟
هر کاربر ترجیح می دهد برنامه ای که با آن در حال کار است، به سرعت به درخواست های او پاسخ بدهد و در زمان پردازش اطلاعات و یا بارگذاری آن ها هنگ نکند. در برنامه هایی که بر روی دسکتاپ اجرا می شوند، معمولا کاربران، صبر بیشتری در صورت اجرا نشدن درست برنامه دارند ولی در مورد برنامه های موبایل اینطور نیست و کاربران انتظار پاسخ سریع و درست دارند. همچنین برای جلب رضایت کاربران در سیستم عامل های جدید، امکانی به کاربران داده شده است که می توانند برنامه هایی که کارایی دستگاه را پایین می آورند، به طور کلی از حالت اجرا خارج کنند.
اگر شما یک برنامه نویس هستید، احتمالا با این مورد مواجه شده اید که ناگهان برنامه تان قادر به واکنش و انجام امور نباشد. یک مثال ساده می تواند زمانی باشد که درخواست های زیادی به سمت سرور فرستاده می شود و پاسخ آن ها بیشتر از حد معمول طول می کشد (که مثلا این مساله می تواند ناشی از مشکلاتی در سمت سرور باشد). اگر این درخواست ها به صورت همزمان فرستاده شده باشند، دستگاه تا زمانی که مشکل خود را برطرف نکند، نمی تواند به هیچ یک از درخواست ها پاسخ بدهد.
برای اینکه بهتر بتوانید این مساله را درک کنید، بیایید نگاه دقیق تری به نحوه ارتباط سیستم عامل با برنامه بیندازیم. زمانی که سیستم عامل می خواهد مواردی را به برنامه اطلاع بدهد، این موارد را با جزئیات کامل آن ها در یک صف قرار می دهد و به سمت برنامه ارسال می کند. این پیام ها در این صف قرار می گیرند و انتظار می کشند تا برنامه ، آن ها را از صف خارج کرده و بخواند.
هر برنامه با رابط کاربری گرافیکی (GUI) یک حلقه مدیریتی اصلی دارد که به صورت پیوسته، محتویات این صف را چک می کند. اگر پیامی در صف وجود داشته باشد که هنوز پردازش نشده باشد، آن را از صف برمیدارد و پردازش می کند. در زبان های سطح بالاتر مانند سی شارپ ، این کار معمولاً به وسیله ی به کارگیری event handler مناسب انجام می شود. این کد در event handler به صورت همزمان اجرا می شود. تا زمانی که این کار به صورت کامل انجام نشده است، سایر پیام ها مورد پردازش قرار نخواهند گرفت. اگر این کار، مدت زمان زیادی طول بکشد، برنامه خطای عدم پاسخگویی را به سمت رابط کاربری ارسال می کند.
برنامه نویسی غیرهمزمان با استفاده از کلمات کلیدی async و await یک راه آسان برای جلوگیری از این مشکل ارائه می کند.
قبل از اینکه ببینیم برنامه نویسی غیرهمزمان چیست، بیایید با استفاده از مثال کنسول زیر بفهمیم برنامه نویسی همزمان (synchronous programming) چیست.
خروجی قطعه کد بالا به شکل زیر است:
در مثال بالا، متد ()LongProcess یک task طولانی مدت است مانند خواندن یک فایل از سرور، فراخوانی یک API وب که حجم زیادی از داده را برمی گرداند یا بارگذاری یا دانلود یک فایل بزرگ. اجرای این task مدت زمان زیادی طول می کشد و دستور Thread.Sleep(4000) آن را به مدت 4 ثانیه نگه می دارد تا زمان اجرای طولانی را نشان دهد. ()ShortProcess یک متد ساده است که پس از متد ()LongProcess اجرا می شود.
برنامه بالا به صورت همزمان اجرا می شود. این بدان معناست که اجرا از متد ()Main شروع می شود که در آن ابتدا متد ()LongProcess و سپس متد ()ShortProcess را اجرا می کند. در طول اجرا، یک برنامه مسدود می شود و پاسخگو نمی باشد (این را می توانید عمدتاً در برنامه های مبتنی بر ویندوز مشاهده کنید). به این سبک از برنامه نویسی، برنامهنویسی همزمان گفته میشود که در آن اجرا به خط بعدی نمیرود تا زمانی که خط فعلی به طور کامل اجرا شود.
برنامه نویسی آسنکرون یا غیرهمزمان چیست؟
در برنامه نویسی غیرهمزمان، کد بدون نیاز به منتظر ماندن برای پایان کار I/O-bound یا task طولانی مدت، در یک thread اجرا می شود. به عنوان مثال، در مدل برنامهنویسی غیرهمزمان، متد ()LongProcess در یک thread مجزا از Thread Pool اجرا میشود و thread برنامه اصلی به اجرای دستور بعدی ادامه میدهد.
مایکروسافت Task-based Asynchronous Pattern را برای اجرای برنامهنویسی غیرهمزمان در برنامههای NET Framework. یا NET Core. با استفاده از کلمات کلیدی async و await و Task یا کلاس <Task<TResult توصیه میکند.
حالا بیایید مثال بالا را در الگوی غیرهمزمان با استفاده از کلمه کلیدی async بازنویسی کنیم.
خروجی قطعه کد بالا عبارت است از:
در مثال بالا، متد ()Main با کلمه کلیدی async مشخص شده است و نوع بازگشتی آن Task است. کلمه کلیدی async متد را به صورت غیرهمزمان علامت گذاری می کند. توجه داشته باشید که برای پیاده سازی برنامه نویسی غیرهمزمان، تمام متدهای زنجیره متد باید ناهمگام باشند. بنابراین، متد ()Main باید ناهمگام باشد تا متدهای فرزند غیرهمزمان شوند.
متد ()LongProcess نیز با کلمه کلیدی async مشخص شده است که آن را غیرهمزمان می کند. await Task.Delay(4000) اجرای thread را به مدت 4 ثانیه نگه می دارد.
اکنون، برنامه از متد ()async Main در thread برنامه اصلی شروع به اجرا می کند. متد ()async LongProcess در یک thread جدا اجرا میشود و thread برنامه اصلی اجرای دستور بعدی را ادامه میدهد که متد ()ShortProcess را فراخوانی میکند و منتظر نمیماند تا ()LongProcess تکمیل شود.
Async ، Await و Task
async و await دو برچسب هستند که مشخص می کنند در کدام بخش کد، پاسخ دهی باید بعد از اتمام کار از سر گرفته شود.
اگر متد async مقداری را به کد فراخوانی برمی گرداند، از async به همراه await و Task استفاده کنید. ما فقط از کلمه کلیدی async در برنامه بالا برای نشان دادن متد غیرهمزمان نوع void استفاده کردیم.
کلمه کلیدی await منتظر متد async می ماند تا زمانی که مقداری را برگرداند. بنابراین thread مربوط به برنامه اصلی در آنجا متوقف می شود تا زمانی که مقدار بازگشتی را دریافت کند.
کلاس Task یک عملیات ناهمگام را نشان می دهد و کلاس عمومی <Task<TResult عملیاتی را نشان می دهد که می تواند یک مقدار را برگرداند. در مثال بالا از await Task.Delay(4000) استفاده کردیم که عملیات async را شروع کرد که به مدت 4 ثانیه میخوابد و await یک thread را تا 4 ثانیه نگه میدارد.
آنچه که در زیر می بینید یک متد async را نشان می دهد که یک مقدار را برمی گرداند.
خروجی قطعه کد بالا عبارت است از:
در مثال بالا، در متد ()static async Task<int> LongProcess، عبارت <Task<int برای نشان دادن نوع مقدار بازگشتی int استفاده می شود. int val = await result نخ اصلی را در آنجا متوقف می کند تا زمانی که مقدار بازگشتی پر شده در result را بدست آورد. پس از دریافت مقدار در متغیر result، به طور خودکار یک عدد صحیح را به val اختصاص می دهد.
یک متد async باید void ، Task یا <Task<TResult را برگرداند، که در آن TResult نوع برگشتی متد async است. نوع بازگشتی void معمولاً برای event handler استفاده می شود. کلمه کلیدی async به ما این امکان را می دهد که از کلمه کلیدی await در متد استفاده کنیم تا بتوانیم منتظر تکمیل متد ناهمگام برای سایر متد هایی باشیم که به مقدار بازگشتی وابسته هستند.
اگر چندین متد ناهمگام دارید که مقادیری را بر می گردانند، می توانید قبل از اینکه بخواهید از مقدار برگشتی در مراحل بعدی استفاده کنید، از await برای همه متدها استفاده کنید.
خروجی قطعه کد بالا عبارت است از:
در برنامه بالا، ما await result1 و await result2 را انجام می دهیم درست قبل از اینکه نیاز به ارسال مقدار بازگشتی به متد دیگری داشته باشیم.
بنابراین، میتوانید از async ، await و Task برای پیادهسازی برنامهنویسی غیرهمزمان در NET Framework. یا NET Core. با استفاده از زبان برنامه نویسی سی شارپ استفاده کنید.