فصل اول: برنامهنویسی ناهمگام و تسکها ⏳🔄
در این فصل، ابتدا با مقدمهای بر برنامهنویسی ناهمگام آشنا میشوید و سپس تسکها (Tasks) و اهمیت آنها بررسی میشوند.
درک عملیات ناهمگام 🧠➡️💻
روانشناسان معمولاً انجام یک کار در یک زمان را توصیه میکنند، زیرا این کار باعث تمرکز بهتر میشود. اما زندگی مدرن پر از مشغله است و پیروی از این توصیه همیشه آسان نیست.
مثالی ساده: مادران شاغل را در نظر بگیرید. آنها صبحها باید چندین کار را قبل از رفتن به محل کار انجام دهند:
-
آماده کردن صبحانه 🍳
-
آماده کردن بچهها برای مدرسه 🎒
-
آماده شدن برای محل کار 👩💼
این مادران کارها را شروع میکنند اما منتظر اتمام یک کار نمیمانند؛ بلکه به کار بعدی میپردازند و سپس برمیگردند تا کار قبلی را بررسی کنند. این روند تا اتمام همه کارها ادامه دارد.
این دقیقاً همان چیزی است که در برنامهنویسی ناهمگام اتفاق میافتد؛ کارها همزمان یا موازی آغاز میشوند و هر کدام در زمان مناسب تکمیل میگردند.
این روش چه کمکی میکند؟ 🤔⚡
حتی اگر این شیوه را دوست نداشته باشید، مواقعی وجود دارد که اجتناب از آن ممکن نیست.
مثال ساده: اگر صبح دیر بیدار شوید اما نمیخواهید برای مدرسه یا محل کار دیر کنید، تنها گزینه این است که چند کار را همزمان انجام دهید (ناهمگام).
حالا بیایید درباره برنامهنویسی صحبت کنیم. فرض کنید در حال توسعه برنامهای هستید که همه کارها، حتی کارهای طولانیمدت (مثلاً دانلود یک فایل حجیم از اینترنت)، روی رشته اصلی برنامه (Main Thread) اجرا میشوند.
-
در این حالت، تا زمانی که رشته اصلی مشغول است، کاربر نمیتواند ورودی جدید بدهد.
-
نتیجه؟ برنامه قفل یا فریز شده به نظر میرسد، و این تجربهای آزاردهنده برای کاربر است.
راهحل چیست؟
-
وظیفه طولانیمدت را به یک رشته پسزمینه منتقل کنید و رشته اصلی را آزاد بگذارید.
-
در این حالت، رشته اصلی میتواند به ورودیهای جدید کاربر پاسخ دهد یا ادامه کارهای قبلی خود را انجام دهد.
نتیجه:
-
برنامه سریعتر، روانتر و کاربرپسندتر میشود.
-
همچنین، رایانههای امروزی دارای چندین هسته پردازشی هستند. با استفاده از برنامهنویسی ناهمگام، میتوانید از تمام ظرفیت سختافزار استفاده کنید و برنامهای کارآمد و بهینه بسازید.
سناریوهای مناسب استفاده از برنامهنویسی ناهمگام 🎯
البته همه موقعیتها مناسب کار ناهمگام نیستند.
-
اگر بچهها خیلی شیطون باشند، شاید مادر مجبور باشد اول یک کار را کامل کند (مثلاً تکالیف بچهها) و بعد سراغ کار دیگر (مثل آشپزی) برود.
-
یا اگر مادر بخواهد فشار کار را کم کند، ممکن است یک معلم خصوصی برای بچهها یا آشپز برای غذا استخدام کند، ولی این کار هزینه اضافه دارد.
-
اگر بودجه محدود باشد، باید همه کارها را خودش و بهصورت ترتیبی (همگام) انجام دهد.
پس نتیجه میگیریم که:
هر موقعیتی مناسب کار ناهمگام یا موازی نیست.
اما سؤال مهم: چه زمانی باید از برنامهنویسی ناهمگام استفاده کنیم؟
موارد رایج:
-
کارهای وابسته به I/O مثل دسترسی به پایگاهداده، خواندن یا نوشتن فایلهای بزرگ، و درخواست داده از اینترنت 🌐
-
کارهای وابسته به CPU مثل انجام محاسبات پیچیده و زمانبر 🖥️
پرسش و پاسخ ❓💬
سؤال 1.1: به نظر میرسد برنامهنویسی ناهمگام باعث افزایش پیچیدگی کد میشود. آیا این مشکل نیست؟ 🤔
بله، درست است که ممکن است پیچیدگی کد افزایش یابد.
اما تصور کنید برای ایجاد یک رابط کاربری (UI) پاسخگوتر، یک عملیات طولانی را به رشته پسزمینه منتقل میکنید.
-
در این حالت، رشته اصلی UI مسدود نمیشود و کاربر تجربه ناخوشایند قفل شدن برنامه را ندارد.
-
در نهایت، کاربران راضیتر خواهند بود و این ارزشمند است.
سؤال 1.2: آیا برنامهنویسی ناهمگام و برنامهنویسی موازی یکی هستند؟ ⚡👥
بیایید چند مثال واقعی بررسی کنیم:
-
برنامهنویسی ناهمگام:
فرض کنید برای شام در حال پختن برنج هستید. بعد از شستن برنج و اضافه کردن آب، آن را روی شعله میگذارید تا آرام بپزد. در این فاصله، تصمیم میگیرید به بچهها تکالیفشان را بسپارید. -
شما بین آشپزخانه و بچهها رفتوآمد دارید تا هر دو کار انجام شود.
-
این شبیه برنامهنویسی ناهمگام است، چون یک کار در حال انجام است و شما میتوانید در زمان انتظار، کارهای دیگر را پیش ببرید.
-
برنامهنویسی موازی:
حالا فرض کنید یک آشپز استخدام کردهاید تا شام را آماده کند، و شما در همان زمان روی تکالیف بچهها تمرکز میکنید. -
در اینجا، دو نفر همزمان دو کار متفاوت را انجام میدهند.
-
این دقیقاً شبیه برنامهنویسی موازی است (مثل داشتن یک رایانه دو هستهای).
-
ترکیب مدلها:
میتوانید چند نفر را در آشپزخانه داشته باشید (آشپز و کمکآشپز) و چند نفر برای تدریس بچهها. -
این همان ترکیب ناهمگام و موازی است که در برنامهنویسی هم انجام میدهیم.
تعریف رسمی:
مجله Visual Studio این موضوع را اینگونه خلاصه کرده است:
برنامهنویسی ناهمگام روشی از برنامهنویسی موازی است که در آن یک واحد کاری بهصورت جداگانه از رشته اصلی برنامه اجرا میشود و پس از اتمام، شکست یا پیشرفت کار، رشته فراخوان را مطلع میکند.
الگوهای برنامهنویسی 🧩💡
بدون شک، برنامهنویسی ناهمگام سخت است، اما در گذشته بسیار سختتر بود. در آن زمان، توسعهدهندگان گزینههای زیر را داشتند:
-
استفاده مستقیم از Thread ها 🧵
-
استفاده مستقیم از کلاس ThreadPool 🔄
-
استفاده از متدهای Callback 📞
-
استفاده از مدل رویدادمحور (Event-based Asynchrony) 🔔
-
استفاده از الگوی AsyncResult 📦
اما این روشها برای توسعههای جدید توصیه نمیشوند.
الگوی پیشنهادی ✨✅
پس، الگوی پیشنهادی چیست؟
به لینک رسمی مایکروسافت مراجعه کنید:
https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap
مایکروسافت چنین توضیح میدهد:
در .NET، الگوی ناهمگام مبتنی بر Task (TAP)، الگوی توصیهشده برای توسعههای جدید است.
این الگو بر اساس Task و Taskدر فضای نام System.Threading.Tasks است که برای نمایش عملیات ناهمگام استفاده میشوند.
توجه:
این کتاب بهصورت عمیق وارد TAP نمیشود، زیرا اکثر پیادهسازیهای TAP شامل کلیدواژههای async و await هستند که در کتابچه دیگری از این مجموعه بررسی خواهند شد.
این کتاب صرفاً روی برنامهنویسی با Task بدون استفاده از async و await تمرکز دارد.
- یادگیری این مطالب، به شما کمک بزرگی خواهد کرد وقتی وارد برنامهنویسی ناهمگام پیشرفته میشوید و از async و await در پروژههای خود بهره میبرید. 🚀
کتابخانه موازیسازی وظایف (Task Parallel Library - TPL) ⚡🧵
در برنامهنویسی با Task، زیاد با اصطلاح Task Parallel Library (TPL) مواجه میشوید. اجازه بدهید یک مرور سریع داشته باشیم:
TPL مجموعهای از نوعها (Types) و APIهای عمومی را ارائه میدهد که در فضاهای نام زیر قرار دارند:
-
System.Threading 🔄
-
System.Threading.Tasks 🧩
جالب است بدانید فضای نام System.Threading.Tasks در .NET 4 معرفی شد و مایکروسافت بیان کرده است:
"TPL، API ترجیحی برای نوشتن کدهای چندریسمانی (Multi-threaded)، ناهمگام (Asynchronous) و موازی (Parallel) در .NET است."
https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl
به همین دلیل، این کتاب بحث را با Taskها شروع میکند، چون آنها پایه و اساس TPL هستند.
پشتصحنه TPL 🔍
جالب است بدانید در پشتصحنه، Taskها در صف ThreadPool قرار میگیرند. این ThreadPool تعداد رشتهها را تعیین و تنظیم میکند و با استفاده از الگوریتمهای مخصوص، بار کاری را متعادل میسازد تا بیشترین بازدهی حاصل شود.
TPL چگونه کمک میکند؟ 🤔
TPL در سناریوهای متنوعی مفید است. به عنوان مثال، میتوانید از آن برای کنترل هر یک از موارد زیر استفاده کنید:
-
مدیریت بهینه یک محیط چندریسمانی 🛠
-
افزایش سطح همزمانی (Concurrency) 📈
-
پشتیبانی از لغو وظایف (Task Cancellation) ⏹
-
مدیریت وضعیت (State Management) 🗂
-
تقسیمبندی کارها (Partitioning) 📦
فعلاً همین اندازه کافی است که بدانید:
TPL فرآیندهای چندریسمانی را سادهتر میکند و به شما کمک میکند کدهای پرفورمنس بالا بنویسید بدون اینکه درگیر جزئیات پیچیده Threading و بخشهای سطح پایین شوید.
مایکروسافت این موضوع را بهخوبی خلاصه کرده است:
هدف TPL این است که با سادهسازی اضافه کردن موازیسازی و همزمانی به برنامهها، بهرهوری توسعهدهندگان را افزایش دهد.
https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl
معرفی Task ها در TPL 🔹
همانطور که دیدید، TPL بر اساس مفهوم Task ساخته شده است. حال سؤال این است:
Task چیست؟
میتوانید Task را مانند یک بلاک کد که یک واحد کار (Unit of Work) را نشان میدهد در نظر بگیرید. شما میتوانید به زمانبند (Scheduler) اطلاع دهید که این بلاک کد میتواند روی یک رشتهی جداگانه (Separate Thread) اجرا شود، در حالی که رشتهی اصلی (Main Thread) به کار خود ادامه میدهد.
به عنوان مثال، قطعه کد زیر را ببینید:
static void PrintNumbersTask()
{
WriteLine("Starts printing the numbers.");
// Continues the work
WriteLine("The task is completed.");
}
این متد یک واحد کاری است که میتواند روی یک رشتهی جداگانه اجرا شود.
سناریوهای مفید استفاده از Taskها 💡
برخی از سناریوهای رایج استفاده از Task شامل موارد زیر است:
-
انجام یک محاسبه و نمایش نتیجه 🔢
-
محاسبه یک مقدار با یا بدون دریافت ورودی ⚙️
-
درخواست یک منبع شبکهای 🌐
-
بررسی سلامت یک وبسایت (برای مثال Ping کردن یک سایت) 📡
پرسش و پاسخ ❓💬
سؤال 1.3: مزایای استفاده از Task نسبت به Thread چیست؟ ⚡🧵
مزایای رایج استفاده از Task شامل موارد زیر است:
-
Taskها سبکتر هستند و به شما کمک میکنند موازیسازی دقیقتر (Fine-grained Parallelism) داشته باشید.
-
با استفاده از API داخلی Taskها، میتوانید به راحتی عملیات مفیدی مثل انتظار (Waiting)، لغو (Cancellation)، ادامه کار (Continuation)، زمانبندی سفارشی (Custom Scheduling) و مدیریت استثناها (Exception Handling) را انجام دهید.
-
بنابراین، وقتی از Task به جای Thread استفاده میکنید، کنترل برنامهنویسی بیشتری خواهید داشت. ✅
سؤال 1.4: پیشرفتهای اساسی Task نسبت به مدلهای برنامهنویسی قبلی چه بود؟ 🚀
مزایای زیادی وجود دارد. پس از اتمام این کتاب، این مزایا برایتان واضح خواهد شد.
فعلاً میتوان گفت:
-
وقتی از Task استفاده میکنید، بعد از شروع عملیات میتوانید به راحتی تولیدکننده Task و مصرفکننده(های) Task را با ارائه ادامه کار (Continuation of Work) به هم متصل کنید.
-
به عبارت دیگر، نیازی نیست ادامه کار را به متدی که عملیات را فراخوانی میکند بدهید.
-
این یکی از مزایای اصلی Task نسبت به مدلهای برنامهنویسی قبلی است. 🌟
خلاصه فصل ۱ 📚
این فصل با بحث برنامهنویسی ناهمگام و کاربردهای آن در دنیای امروزی آغاز شد. سپس TPL و نقش Taskها در برنامهنویسی ناهمگام معرفی شد.
به طور خلاصه، این فصل به سوالات زیر پاسخ داد:
-
برنامهنویسی ناهمگام چیست و چرا مهم است؟ 🤔
-
سناریوهای مناسب برای برنامهنویسی ناهمگام کدامند؟ ⚡
-
TPL چیست؟ 🧵
-
Task چیست و چرا نسبت به Thread مفید است؟ ✅
تمرینها 📝💡
برای سنجش درک خود، تمرینهای زیر را انجام دهید:
E1.1 درست/نادرست (True/False):
i) در .NET، الگوی ناهمگام مبتنی بر Task، الگوی توصیهشده فعلی برای برنامهنویسی ناهمگام است. ✅
ii) اجرای ناهمگام همیشه سریعتر از اجرای همزمان (Synchronous) مربوطه است. ❌
iii) انجام یک محاسبه زمانبر نمونهای از عملیات CPU-bound است که میتواند از برنامهنویسی ناهمگام بهره ببرد. ✅
iv) دانلود یک فایل بزرگ از اینترنت نمونهای رایج از عملیات I/O-bound است که میتواند از برنامهنویسی ناهمگام بهره ببرد. ✅
E1.2 حداقل دو مزیت اصلی برنامهنویسی ناهمگام را بیان کنید:
-
میتوانید کارهای طولانی را به رشتههای پسزمینه منتقل کنید و در نتیجه رابط کاربری پاسخگوتر بسازید. 🌟
-
با استفاده از برنامهنویسی ناهمگام، میتوانید از سختافزار مدرن بهطور کامل استفاده کنید و سرعت برنامه خود را افزایش دهید. ⚡
راهحل تمرینها ✅💡
در اینجا نمونهای از پاسخهای تمرینهای فصل ۱ ارائه شده است:
E1.1
پاسخها به صورت Bold مشخص شدهاند:
i) در .NET، الگوی ناهمگام مبتنی بر Task، الگوی توصیهشده فعلی برای برنامهنویسی ناهمگام است. [True ✅]
ii) اجرای ناهمگام همیشه سریعتر از اجرای همزمان (Synchronous) مربوطه است. [False ❌]
iii) انجام یک محاسبه زمانبر نمونهای از عملیات CPU-bound است که میتواند از برنامهنویسی ناهمگام بهره ببرد. [True ✅]
iv) دانلود یک فایل بزرگ از اینترنت نمونهای رایج از عملیات I/O-bound است که میتواند از برنامهنویسی ناهمگام بهره ببرد. [True ✅]
E1.2
دو مزیت اصلی استفاده از برنامهنویسی ناهمگام عبارتند از:
-
میتوانید کارهای طولانی را به رشتههای پسزمینه منتقل کنید و در نتیجه رابط کاربری پاسخگوتر بسازید. 🌟
-
با استفاده از برنامهنویسی ناهمگام، میتوانید از سختافزار مدرن بهطور کامل استفاده کنید و سرعت برنامه خود را افزایش دهید. ⚡