Farhad Nosrati فرهاد نصرتی
Farhad Nosrati فرهاد نصرتی
خواندن ۱۵ دقیقه·۱۴ روز پیش

Task-based asynchronous programming

کتابخانه موازی وظیفه (Task Parallel Library یا TPL) بر پایه مفهومی به نام "وظیفه" (Task) طراحی شده است که نشان‌دهنده یک عملیات غیرهمزمان است. از برخی جنبه‌ها، یک وظیفه شبیه به یک نخ (Thread) یا یک آیتم کاری در ThreadPool است، اما در سطحی بالاتر از انتزاع عمل می‌کند. اصطلاح "موازی‌سازی وظیفه" (Task Parallelism) به اجرای همزمان یک یا چند وظیفه مستقل اشاره دارد. وظایف (Tasks) دو مزیت اصلی ارائه می‌دهند:

1. استفاده کارآمدتر و مقیاس‌پذیرتر از منابع سیستم

در پشت صحنه، وظایف به صف ThreadPool ارسال می‌شوند که این صف به وسیله الگوریتم‌هایی بهینه‌سازی شده است که تعداد نخ‌ها را تعیین و تنظیم می‌کنند. این الگوریتم‌ها تعادل بار (Load Balancing) را مدیریت می‌کنند تا بیشترین کارایی حاصل شود. این فرآیند وظایف را سبک و کم‌هزینه می‌کند و این امکان را می‌دهد که تعداد زیادی از آن‌ها ایجاد کنید تا موازی‌سازی جزئی‌تر و دقیق‌تری داشته باشید.

2. کنترل بیشتر بر فرآیند نسبت به نخ‌ها یا آیتم‌های کاری

وظایف و چارچوبی که حول آن‌ها ساخته شده است، مجموعه گسترده‌ای از APIها را ارائه می‌دهد که شامل قابلیت‌هایی مانند انتظار (Waiting)، لغو (Cancellation)، ادامه (Continuations)، مدیریت استثنائات قوی، گزارش وضعیت دقیق، زمان‌بندی سفارشی و بسیاری ویژگی‌های دیگر است.

به همین دلایل، TPL بهترین گزینه برای نوشتن کدهای چندنخی، غیرهمزمان، و موازی در دات‌نت (.NET) است.

ایجاد و اجرای وظایف به صورت غیرمستقیم

روش Parallel.Invoke یک راه ساده برای اجرای همزمان چندین دستور مختلف ارائه می‌دهد. شما کافی است یک Action برای هر عملیات کاری ارائه دهید. ساده‌ترین روش برای تعریف این Actionها استفاده از عبارت‌های لامبدا (Lambda Expressions) است. این عبارت‌های لامبدا می‌توانند یک متد نام‌گذاری‌شده را فراخوانی کنند یا کد موردنظر را به صورت مستقیم ارائه دهند.

در مثال زیر، یک فراخوانی ابتدایی به Parallel.Invoke نشان داده شده است که دو وظیفه ایجاد و اجرا می‌کند که به صورت همزمان اجرا می‌شوند. وظیفه اول یک عبارت لامبدا است که متدی به نام DoSomeWork را فراخوانی می‌کند و وظیفه دوم نیز متدی به نام DoSomeOtherWork را فراخوانی می‌کند:

Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());

نکته : این مستندات از عبارات لامبدا (Lambda Expressions) برای تعریف نماینده‌ها (Delegates) در TPL استفاده می‌کند. اگر با عبارات لامبدا در C# یا Visual Basic آشنایی ندارید، پیشنهاد می‌کنم به مستندات Lambda Expressions in PLINQ and TPL مراجعه کنید.

نکته : تعداد نمونه‌های وظیفه (Task Instances) که در پس‌زمینه توسط متد Invoke ایجاد می‌شوند، لزوماً برابر با تعداد نماینده‌هایی نیست که ارائه داده‌اید. TPL ممکن است بهینه‌سازی‌های مختلفی انجام دهد، به خصوص زمانی که تعداد نماینده‌ها زیاد باشد.


یک وظیفه که مقدار بازگشتی ندارد، با کلاس System.Threading.Tasks.Task نمایش داده می‌شود. وظیفه‌ای که یک مقدار بازگشتی دارد، با کلاس System.Threading.Tasks.Task<TResult> نمایش داده می‌شود که از کلاس Task به ارث می‌برد. شیء وظیفه (Task Object) مدیریت جزئیات زیرساخت را انجام می‌دهد و متدها و خواصی را ارائه می‌دهد که در طول عمر وظیفه از نخ فراخوانی قابل دسترسی هستند. به عنوان مثال، شما می‌توانید در هر زمانی به ویژگی Status یک وظیفه دسترسی داشته باشید تا مشخص کنید که آیا وظیفه شروع به اجرا کرده، به پایان رسیده، لغو شده یا استثنایی (Exception) رخ داده است. وضعیت وظیفه با یک شمارش به نام TaskStatus نمایش داده می‌شود.

هنگامی که یک وظیفه ایجاد می‌کنید، باید یک نماینده (Delegate) به آن بدهید که کدی را که وظیفه اجرا خواهد کرد، در بر می‌گیرد. این نماینده می‌تواند به صورت یک نماینده نام‌گذاری‌شده، یک متد ناشناس یا یک عبارت لامبدا (Lambda Expression) تعریف شود. عبارات لامبدا می‌توانند شامل فراخوانی به یک متد نام‌گذاری‌شده باشند، همان‌طور که در مثال زیر نشان داده شده است. این مثال شامل فراخوانی متد Task.Wait است تا اطمینان حاصل شود که وظیفه قبل از پایان برنامه در حالت کنسول اجرا را کامل می‌کند.

Task taskA = new Task( () => Console.WriteLine(&quotHello from taskA.&quot));
taskA.Start();
taskA.Wait();

شما می‌توانید از متدهای Task.Run برای ایجاد و شروع یک وظیفه به صورت همزمان استفاده کنید. متدهای Run همیشه از زمان‌بند پیش‌فرض وظایف استفاده می‌کنند، حتی اگر نخ فعلی از زمان‌بند دیگری استفاده کند.

متدهای Run گزینه مناسبی هستند زمانی که نیازی به کنترل بیشتر روی ایجاد و زمان‌بندی وظایف ندارید.

Task taskA = Task.Run( () => Console.WriteLine(&quotHello from taskA.&quot));
taskA.Wait();

شما همچنین می‌توانید از متد TaskFactory.StartNew برای ایجاد و شروع یک وظیفه به صورت همزمان استفاده کنید. همانطور که در مثال زیر نشان داده شده است، می‌توانید از این متد زمانی استفاده کنید که:

  • ایجاد و زمان‌بندی نیازی به تفکیک نداشته باشند و شما به گزینه‌های اضافی برای ایجاد وظیفه یا استفاده از یک زمان‌بند خاص نیاز داشته باشید.
  • نیاز دارید که وضعیت اضافی را به وظیفه ارسال کنید که بتوانید از طریق ویژگی Task.AsyncState آن را بازیابی کنید.

Task<Double>.Factory.StartNew(() => DoComputation(1000.0));


برای اطلاعات بیشتر، به مستندات زیر مراجعه کنید.

https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-return-a-value-from-a-task

زمانی که از یک عبارت لامبدا برای ایجاد یک نماینده استفاده می‌کنید، به تمامی متغیرهایی که در آن نقطه از کد شما قابل مشاهده هستند دسترسی دارید. با این حال، در برخی موارد، به‌ویژه در داخل حلقه‌ها، لامبدا متغیر را به‌طور مورد انتظار ذخیره نمی‌کند. لامبدا فقط مرجع (Reference) متغیر را ذخیره می‌کند، نه مقدار آن، چون این متغیر بعد از هر بار تکرار تغییر می‌کند.


شما می‌توانید با فراهم کردن یک شیء وضعیت (state object) به یک وظیفه از طریق سازنده آن، به مقدار در هر تکرار دسترسی پیدا کنید.

این وضعیت به‌عنوان یک آرگومان به نماینده وظیفه (task delegate) ارسال می‌شود و می‌توان از طریق ویژگی Task.AsyncState آن را از شیء وظیفه دسترسی کرد.

Task ID

هر وظیفه یک شناسه عددی دریافت می‌کند که آن را به‌طور منحصر به فرد در دامنه برنامه شناسایی می‌کند و می‌توان از طریق ویژگی Task.Id به آن دسترسی پیدا کرد. این شناسه برای مشاهده اطلاعات وظایف در پنجره‌های Parallel Stacks و Tasks در دیباگر ویژوال استودیو مفید است. شناسه به‌طور تنبل (lazy) ایجاد می‌شود، به این معنی که تا زمانی که درخواست نشود، ایجاد نمی‌شود. بنابراین، ممکن است هر بار که برنامه اجرا می‌شود، شناسه متفاوتی به وظیفه اختصاص یابد. برای اطلاعات بیشتر در مورد نحوه مشاهده شناسه‌های وظیفه در دیباگر، به مستندات زیر مراجعه کنید.

https://learn.microsoft.com/en-us/visualstudio/debugger/using-the-parallel-stacks-window?view=vs-2022
https://learn.microsoft.com/en-us/visualstudio/debugger/using-the-tasks-window?view=vs-2022

Task creation options

بیشتر APIهایی که وظایف ایجاد می‌کنند، اضافه‌بارهایی دارند که یک پارامتر از نوع TaskCreationOptions را قبول می‌کنند. با مشخص کردن یک یا چند گزینه از این موارد، شما به زمان‌بند وظیفه می‌گویید که چگونه وظیفه را روی استخر نخ زمان‌بندی کند. این گزینه‌ها ممکن است با استفاده از عملگر OR بیتی ترکیب شوند.

مثال زیر یک وظیفه را نشان می‌دهد که گزینه‌های LongRunning و PreferFairness را دارد.

var task3 = new Task(() => MyLongRunningMethod(), TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness); task3.Start();

Tasks, threads, and culture

هر نخ یک فرهنگ و فرهنگ UI مرتبط دارد که به ترتیب توسط ویژگی‌های Thread.CurrentCulture و Thread.CurrentUICulture تعریف می‌شود. فرهنگ یک نخ در عملیاتی مانند قالب‌بندی، تجزیه و تحلیل، مرتب‌سازی و مقایسه رشته‌ها استفاده می‌شود. فرهنگ UI یک نخ در جستجوی منابع استفاده می‌شود.

فرهنگ سیستم فرهنگ پیش‌فرض و فرهنگ UI پیش‌فرض نخ را تعریف می‌کند. با این حال، می‌توانید یک فرهنگ پیش‌فرض برای تمام نخ‌ها در یک دامنه برنامه از طریق ویژگی‌های CultureInfo.DefaultThreadCurrentCulture و CultureInfo.DefaultThreadCurrentUICulture مشخص کنید. اگر فرهنگ یک نخ را به‌طور صریح تنظیم کنید و یک نخ جدید راه‌اندازی کنید، نخ جدید فرهنگ نخ فراخوانی را به ارث نمی‌برد؛ در عوض، فرهنگ آن فرهنگ پیش‌فرض سیستم است. با این حال، در برنامه‌نویسی مبتنی بر وظیفه (Task)، وظایف از فرهنگ نخ فراخوانی استفاده می‌کنند، حتی اگر وظیفه به‌صورت غیرهمزمان روی نخ دیگری اجرا شود.

مثال زیر یک توضیح ساده ارائه می‌دهد. این مثال فرهنگ فعلی برنامه را به فرانسوی (فرانسه) تغییر می‌دهد. اگر فرانسوی (فرانسه) قبلاً فرهنگ فعلی باشد، به انگلیسی (ایالات متحده) تغییر می‌دهد. سپس یک نماینده به نام formatDelegate فراخوانی می‌شود که برخی از اعداد را به‌عنوان مقادیر ارزی در فرهنگ جدید فرمت می‌کند. چه نماینده به‌طور همزمان یا غیرهمزمان توسط یک وظیفه فراخوانی شود، وظیفه از فرهنگ نخ فراخوانی استفاده می‌کند.

نکته : در نسخه‌های پیش از .NET Framework 4.6، فرهنگ یک وظیفه توسط فرهنگ نخی که وظیفه روی آن اجرا می‌شود تعیین می‌شود، نه فرهنگ نخ فراخوانی. برای وظایف غیرهمزمان، فرهنگ استفاده‌شده توسط وظیفه ممکن است با فرهنگ نخ فراخوانی متفاوت باشد.

Creating task continuations

متدهای Task.ContinueWith و Task<TResult>.ContinueWith به شما این امکان را می‌دهند که یک وظیفه را مشخص کنید که پس از اتمام وظیفه قبلی اجرا شود. نماینده وظیفه ادامه‌دهنده یک مرجع به وظیفه قبلی دریافت می‌کند تا بتواند وضعیت آن را بررسی کند. و با بازیابی مقدار ویژگی Task<TResult>.Result، می‌توانید از خروجی وظیفه قبلی به‌عنوان ورودی برای وظیفه ادامه‌دهنده استفاده کنید.

چون Task.ContinueWith یک متد نمونه‌ای است، شما می‌توانید فراخوانی‌های متد را به هم متصل کنید، به جای اینکه برای هر وظیفه قبلی یک شیء Task<TResult> ایجاد کنید.

Creating detached child tasks

زمانی که کدی که در یک وظیفه در حال اجرا است، یک وظیفه جدید ایجاد می‌کند و گزینه AttachedToParent را مشخص نمی‌کند، وظیفه جدید به‌طور خاص با وظیفه والد هماهنگ نمی‌شود. این نوع وظیفه غیرهماهنگ به نام "وظیفه تو در تو جدا شده" یا "وظیفه فرزند جدا شده" شناخته می‌شود.


Creating child tasks

زمانی که کدی که در یک وظیفه در حال اجرا است، یک وظیفه با گزینه AttachedToParent ایجاد می‌کند، وظیفه جدید به عنوان "وظیفه فرزند متصل" به وظیفه والد شناخته می‌شود. شما می‌توانید از گزینه AttachedToParent برای بیان موازی‌سازی وظایف ساختاریافته استفاده کنید، زیرا وظیفه والد به‌طور ضمنی منتظر اتمام تمام وظایف فرزند متصل می‌ماند. در مثال زیر، یک وظیفه والد ایجاد می‌شود که ۱۰ وظیفه فرزند متصل ایجاد می‌کند. در این مثال، متد Task.Wait برای انتظار برای اتمام وظیفه والد فراخوانی می‌شود و نیازی به انتظار صریح برای اتمام وظایف فرزند متصل نیست.

Waiting for tasks to finish

انتظار برای اتمام وظایف
انواع System.Threading.Tasks.Task و System.Threading.Tasks.Task<TResult> چندین باراضافه از متدهای Task.Wait را فراهم می‌کنند که به شما امکان می‌دهند برای اتمام یک وظیفه صبر کنید. علاوه بر این، باراضافه‌های متدهای ایستا Task.WaitAll و Task.WaitAny به شما این امکان را می‌دهند که برای اتمام هرکدام یا همه وظایف در یک آرایه از وظایف صبر کنید.

به‌طور معمول، شما برای یکی از این دلایل برای اتمام یک وظیفه صبر می‌کنید:

  1. نخ اصلی به نتیجه نهایی محاسبه‌شده توسط یک وظیفه وابسته است.
  2. باید استثناهایی که ممکن است از وظیفه پرتاب شوند را مدیریت کنید.
  3. ممکن است برنامه قبل از اتمام تمام وظایف به اتمام برسد. به‌عنوان مثال، برنامه‌های کنسولی پس از اجرای تمام کدهای همگام در متد Main (نقطه ورودی برنامه) خاتمه می‌یابند.

مثال زیر الگوی پایه‌ای را نشان می‌دهد که شامل مدیریت استثنا نیست:

برای مثالی که مدیریت استثناها را نشان می‌دهد، به "مدیریت استثناها" مراجعه کنید.

برخی از باراضافه‌ها به شما این امکان را می‌دهند که یک زمان‌سنج تعیین کنید، و برخی دیگر یک پارامتر اضافی به نام CancellationToken دریافت می‌کنند تا بتوانید خود عمل انتظار را هم به صورت برنامه‌نویسی و هم به پاسخ به ورودی کاربر لغو کنید.

وقتی برای یک وظیفه صبر می‌کنید، به‌طور ضمنی برای تمام فرزندان آن وظیفه که با استفاده از گزینه TaskCreationOptions.AttachedToParent ایجاد شده‌اند، صبر می‌کنید. متد Task.Wait بلافاصله اگر وظیفه قبلاً تکمیل شده باشد، بازمی‌گردد. متد Task.Wait هر استثنایی که توسط یک وظیفه پرتاب شده باشد را پرتاب می‌کند، حتی اگر متد Task.Wait پس از اتمام وظیفه فراخوانی شده باشد.


Composing tasks

کلاس‌های Task و Task<TResult> چندین متد را برای کمک به ترکیب چندین تسک فراهم می‌کنند. این متدها الگوهای رایج را پیاده‌سازی کرده و از ویژگی‌های زبان‌های C#، Visual Basic و F# برای برنامه‌نویسی غیرهمزمان به‌خوبی استفاده می‌کنند. در این بخش متدهای WhenAll، WhenAny، Delay و FromResult توضیح داده شده‌اند.

Task.WhenAll


متد Task.WhenAll به‌صورت غیرهمزمان منتظر می‌ماند تا چندین شیء Task یا Task<TResult> تمام شوند. این متد نسخه‌های اضافه‌شده‌ای دارد که به شما این امکان را می‌دهد تا منتظر مجموعه‌های غیر یکنواختی از تسک‌ها باشید. به‌عنوان مثال، می‌توانید منتظر بمانید تا چندین شیء Task و Task<TResult> از یک فراخوانی متد به پایان برسند.

Task.WhenAny


متد Task.WhenAny به‌صورت غیرهمزمان منتظر می‌ماند تا یکی از چندین شیء Task یا Task<TResult> تمام شود. مشابه متد Task.WhenAll، این متد نسخه‌های اضافه‌شده‌ای دارد که به شما این امکان را می‌دهد تا منتظر مجموعه‌های غیر یکنواختی از تسک‌ها باشید. متد WhenAny به‌ویژه در سناریوهای زیر مفید است:

عملیات‌های اضافی: فرض کنید الگوریتم یا عملیاتی دارید که می‌تواند به طرق مختلف انجام شود. می‌توانید از متد WhenAny برای انتخاب عملیاتی که زودتر تمام می‌شود استفاده کنید و سپس عملیات‌های باقی‌مانده را لغو کنید.

عملیات‌های درهم: می‌توانید چندین عملیات را شروع کنید که باید تمام شوند و از متد WhenAny برای پردازش نتایج هر عملیات به محض اتمام آن استفاده کنید. پس از اتمام یک عملیات، می‌توانید یک یا چند تسک را آغاز کنید.

عملیات‌های محدود شده: می‌توانید از متد WhenAny برای گسترش سناریو قبلی با محدود کردن تعداد عملیات‌های همزمان استفاده کنید.

عملیات‌های منقضی‌شده: می‌توانید از متد WhenAny برای انتخاب بین یک یا چند تسک و یک تسک که بعد از زمان خاصی تمام می‌شود استفاده کنید، مانند تسکی که از متد Delay برمی‌گردد. متد Delay در بخش بعدی توضیح داده شده است.

Task.Delay


متد Task.Delay شیء Task ایجاد می‌کند که پس از مدت زمان مشخصی به پایان می‌رسد. می‌توانید از این متد برای ساخت حلقه‌هایی که داده‌ها را به‌طور دوره‌ای بررسی می‌کنند، تعیین زمان‌گذاری‌ها، به تأخیر انداختن پردازش ورودی کاربر و غیره استفاده کنید.

Task<TResult>.FromResult


با استفاده از متد Task.FromResult می‌توانید شیء Task<TResult> ایجاد کنید که یک نتیجه پیش‌محاسبه‌شده را نگه می‌دارد. این متد زمانی مفید است که عملیاتی غیرهمزمان انجام می‌دهید که شیء Task<TResult> باز می‌گرداند و نتیجه آن Task<TResult> قبلاً محاسبه شده باشد. برای مثال، از FromResult می‌توان برای دریافت نتایج عملیات دانلود غیرهمزمان که در کش نگهداری می‌شوند، استفاده کرد.

Handling exceptions in tasks


هنگامی که یک تسک یک یا چند استثنا پرتاب می‌کند، استثناها در یک استثنای AggregateException بسته می‌شوند. این استثنا به رشته‌ای که با تسک ملحق شده است، منتقل می‌شود. معمولاً این رشته‌ای است که منتظر پایان تسک یا دسترسی به ویژگی Result است. این رفتار سیاست .NET Framework را اعمال می‌کند که طبق آن، تمام استثناهای بدون پردازش به‌طور پیش‌فرض باید فرآیند را خاتمه دهند. کد فراخوانی می‌تواند استثناها را با استفاده از هر یک از موارد زیر در یک بلوک try/catch مدیریت کند:

متد Wait

متد WaitAll

متد WaitAny

ویژگی Result

رشته ملحق‌شده همچنین می‌تواند استثناها را با دسترسی به ویژگی Exception قبل از جمع‌آوری زباله‌ها مدیریت کند. با دسترسی به این ویژگی، از انتقال استثناهای بدون پردازش که باعث خاتمه فرآیند هنگام نهایی شدن شیء می‌شود، جلوگیری می‌کنید.

برای اطلاعات بیشتر در مورد استثناها و تسک‌ها، به مدیریت استثناها مراجعه کنید.

Canceling tasks


کلاس Task از لغو همکاری پشتیبانی می‌کند و به‌طور کامل با کلاس‌های System.Threading.CancellationTokenSource و System.Threading.CancellationToken که در .NET Framework 4 معرفی شده‌اند، یکپارچه است. بسیاری از سازنده‌های کلاس System.Threading.Tasks.Task یک شیء CancellationToken را به‌عنوان پارامتر ورودی می‌پذیرند. بسیاری از اضافه‌شده‌های متدهای StartNew و Run نیز شامل پارامتر CancellationToken هستند.

شما می‌توانید توکن را ایجاد کرده و درخواست لغو را در زمان بعدی با استفاده از کلاس CancellationTokenSource صادر کنید. توکن را به‌عنوان آرگومان به تسک ارسال کرده و همچنین از همان توکن در دلیگیت کاربر خود استفاده کنید که وظیفه پاسخ به درخواست لغو را انجام می‌دهد.

برای اطلاعات بیشتر، به لغو تسک‌ها و نحوه لغو یک تسک و فرزندان آن مراجعه کنید.

The TaskFactory class


کلاس TaskFactory متدهای ایستا را فراهم می‌کند که الگوهای رایج برای ایجاد و شروع تسک‌ها و تسک‌های ادامه‌ای را در بر می‌گیرد.

رایج‌ترین الگو، StartNew است که در یک بیانیه، تسک را ایجاد و شروع می‌کند.

هنگامی که تسک‌های ادامه‌ای از چندین پیش‌نیاز ایجاد می‌کنید، از متدهای ContinueWhenAll یا ContinueWhenAny یا معادل‌های آنها در کلاس Task<TResult> استفاده کنید. برای اطلاعات بیشتر، به زنجیره‌ای کردن تسک‌ها با استفاده از تسک‌های ادامه‌ای مراجعه کنید.

برای گنجاندن متدهای BeginX و EndX مدل برنامه‌نویسی غیرهمزمان در یک شیء Task یا Task<TResult>، از متدهای FromAsync استفاده کنید. برای اطلاعات بیشتر، به TPL و برنامه‌نویسی غیرهمزمان سنتی .NET Framework مراجعه کنید.

و TaskFactory پیش‌فرض می‌تواند به‌عنوان یک ویژگی ایستا در کلاس Task یا Task<TResult> دسترسی داشته باشد. همچنین می‌توانید یک TaskFactory را مستقیماً نمونه‌سازی کرده و گزینه‌های مختلفی از جمله یک توکن لغو، گزینه‌های TaskCreationOptions، گزینه‌های TaskContinuationOptions یا یک TaskScheduler را مشخص کنید. هر گزینه‌ای که هنگام ایجاد کارخانه تسک مشخص کنید، به تمام تسک‌هایی که ایجاد می‌کند اعمال می‌شود، مگر اینکه تسک با استفاده از enumeration TaskCreationOptions ایجاد شود، در این صورت گزینه‌های تسک گزینه‌های کارخانه تسک را لغو می‌کنند.

Tasks without delegates


در برخی موارد، ممکن است بخواهید از یک تسک برای دربر گرفتن عملیاتی غیرهمزمان که توسط یک مؤلفه خارجی انجام می‌شود به جای دلیگیت کاربر خود استفاده کنید. اگر عملیات بر اساس الگوی Begin/End مدل برنامه‌نویسی غیرهمزمان باشد، می‌توانید از متدهای FromAsync استفاده کنید. اگر اینطور نباشد، می‌توانید از شیء TaskCompletionSource<TResult> برای پیچیدن عملیات در یک تسک استفاده کنید و به این ترتیب از برخی مزایای برنامه‌نویسی تسک‌ها بهره‌مند شوید. به‌عنوان مثال، پشتیبانی از انتقال استثنا و تسک‌های ادامه‌ای. برای اطلاعات بیشتر، به TaskCompletionSource<TResult> مراجعه کنید.

Custom schedulers


بیشتر توسعه‌دهندگان برنامه یا کتابخانه نیازی به دانستن اینکه تسک روی کدام پردازنده اجرا می‌شود، چگونه کار خود را با سایر تسک‌ها هماهنگ می‌کند یا چگونه روی System.Threading.ThreadPool زمان‌بندی می‌شود، ندارند. آنها فقط نیاز دارند که تسک به‌طور کارآمد بر روی کامپیوتر میزبان اجرا شود. اگر نیاز به کنترل دقیق‌تری بر جزئیات زمان‌بندی دارید، TPL به شما این امکان را می‌دهد که برخی تنظیمات را روی زمان‌بند پیش‌فرض تسک پیکربندی کنید و حتی به شما اجازه می‌دهد که یک زمان‌بند سفارشی ارائه دهید. برای اطلاعات بیشتر، به TaskScheduler مراجعه کنید.

Related data structures

ث TPL چندین نوع جدید عمومی را فراهم می‌کند که در سناریوهای موازی و ترتیبی مفید هستند. این شامل. چندین کلاس مجموعه thread-safe، سریع و مقیاس‌پذیر در فضای نام System.Collections.Concurrent و چندین نوع همگام‌سازی جدید است. برای مثال، System.Threading.Semaphore و System.Threading.ManualResetEventSlim که برای برخی از انواع بارها کارآمدتر از نسخه‌های قبلی خود هستند. انواع جدید دیگر در .NET Framework 4، مانند System.Threading.Barrier و System.Threading.SpinLock، عملکردهایی را فراهم می‌کنند که در نسخه‌های قبلی موجود نبودند. برای اطلاعات بیشتر، به ساختار داده‌ها برای برنامه‌نویسی موازی مراجعه کنید.

Custom task types


ما توصیه می‌کنیم که از کلاس‌های System.Threading.Tasks.Task یا System.Threading.Tasks.Task<TResult> ارث‌بری نکنید. به‌جای آن، توصیه می‌کنیم که از ویژگی AsyncState برای ارتباط دادن داده‌ها یا وضعیت اضافی با شیء Task یا Task<TResult> استفاده کنید. همچنین می‌توانید از متدهای افزونه برای گسترش عملکرد کلاس‌های Task و Task<TResult> استفاده کنید. برای اطلاعات بیشتر در مورد متدهای افزونه، به متدهای افزونه مراجعه کنید.

رفرنس :

https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming
taskasyncasynchronousconcurrencyparallelism
https://github.com/nosratifarhad
شاید از این پست‌ها خوشتان بیاید