مثالی ساده از Async و Await در برنامهنویسی غیر همزمان (Asynchronous)
به نام خدا و سلام
در این مقاله مثالی ساده از نحوه استفاده از کلیدواژههای async و await در #C جهت ساخت وظایف پسزمینه غیرهمزمان ارائه خواهد شد.
نوشتن کدهای چندنخی (Multi-threaded) یا غیرهمزمان (Asynchronous) از قدیم کاری سخت اما به شدت مورد نیاز جهت پاسخگو (Responsive) نگه داشتن نرمافزارها و جلوگیری از کاهش کارایی آنها بوده است. نسخه پنجم زبان برنامهنویسی #C مدلی سادهشده برای برنامهنویسی غیرهمزمان با معرفی دو کلیدواژه async و await ارائه کرده است.
اگر شما متدی را با استفاده از modifier (اصلاحگر) async به عنوان یک متد غیرهمزمان مشخص کرده باشید، دو قابلیت زیر فعال خواهند شد:
- متد علامتگذاری شده با async توانایی استفاده از کلیدواژه await جهت برگزیدن نقطههای تعلیق را خواهد داشت. عملگر await به کامپایلر اعلام میکند که متد async (متد علامتگذاری شده با async) نمیتواند از آن نقطه جلوتر رود تا زمانی که پردازش غیرهمزمان در انتظار (awaited asynchronous process) تکمیل شود. در این حین کنترل نرمافزار به متد فراخوانی کننده متد async برمیگردد. تعلیق متد async در یک عبارت await به معنای خروج از آن متد نبوده و بلاکهای نهایی را اجرا نمیکند.
- متد علامتگذاری شده با async خود میتواند توسط متدهای فراخوانی کننده دیگر در انتظار قرار گیرد (یا اصطلاحاً await شود).
یک متد async معمولاً شامل یک یا چند عملگر await میشود، اما عدم وجود عملگر await در آن باعث بروز خطای کامپایلر نخواهد شد.
عدم استفاده از عملگر await در متد async جهت مشخص نمودن نقاط تعلیق علیرغم استفاده از اصلاحگر async باعث اجرا شدن آن به عنوان یک متد همزمان خواهد شد. کامپایلر برای چنین متدهایی یک هشدار صادر میکند.
غیرهمزمانی برای فعالیتهایی که به صورت بالقوه مسدود کنندهاند ضروری است، مانند زمانی که نرمافزار شما قصد دسترسی به یک فایل در اینترنت یا فایل سیستم را دارد. مثلاً دسترسی به یک منبع اینترنتی برخی مواقع کند یا با تاخیر است. اگر چنین فعالیتهایی توسط یک پردازش همزمان مسدود شود کل نرمافزار باید برای آن صبر کند. در یک پردازش غیرهمزمان، نرمافزار میتواند به کارهای دیگری که به منبع اینترنتی مورد نظر وابسته نیست ادامه دهد تا زمانی که فعالیت بالقوه مسدود کننده (در اینجا دسترسی به منبع اینترنتی) تمام شود.
به مثال سادهی زیر توجه کنید:
using System;
using System.Threading.Tasks;
namespace AsyncAwaitExample
{
class Program
{
static void Main()
{
var demo = new AsyncAwaitDemo();
demo.DoStuff();
while (true)
{
Console.WriteLine("Doing Stuff on the Main Thread...................");
}
}
}
public class AsyncAwaitDemo
{
public async Task DoStuff()
{
await Task.Run(() =>
{
LongRunningOperation();
});
}
private static async Task<string> TaskLongRunningOperation()
{
int counter;
for (counter = 0; counter < 50000; counter++)
{
Console.WriteLine(counter);
}
return "Counter = " + counter;
}
}
این برنامه شامل کلاسی به نام AsyncAwaitDemo است. در این کلاس متدی عمومی به نام ()DoStuff وجود دارد که عملیاتی طولانی به نام ()LongRunningOperation را اجرا میکند. این متد با کلیدواژه async مشخص شده است. در این مثال ساده متد ()LongRunningOperation فقط تا 50000 را شمارش نموده و شمارگر را در کنسول چاپ میکند.
به متد ()Main برمیگردیم، پس از اجرای متد ()DoStuff وارد یک حلقه بینهایت خواهیم شد که عمل پرینت را در کنسول انجام میدهد. وقتی برنامه را اجرا میکنید چنین چیزی خواهید دید:
همانطور که میبینید حلقهی بینهایت در حال اجراست اما نکته مهم اجرای ()LongRunningOperation و انجام عمل شمارش تا 50000 در پس زمینه است.
بیایید کمی کد را با حذف Task.Run تغییر دهیم، بنابراین متد به اینصورت خواهد شد:
public async Task DoStuff()
{
LongRunningOperation();
}
که درصورت اجرای برنامه چنین نتیجهای خواهیم دید:
به دلیل حذف کلیدواژه await، متد ()LongRunningOperation به صورت همزمان اجرا میشود، بنابراین ابتدا شمارشگر تا 50000 شمارش خواهد کرد و پس از آن وارد حلقه خواهد شد.
از Void Async استفاده نکنید
سه نوع بازگشتی ممکن برای یک متد async وجود دارد:
- Task
- Task <T>
- Void
انواع بازگشتی اصلی متد async فقط Task و <Task <T هستند. هنگامی که یک کد همزمان را به یک کد غیرهمزمان تبدیل میکنید تمام متدهایی که نوعی را برمیگردانند به متد async با نوع بازگشتی <Task <T تبدیل شده و تمام متدهایی که void برمیگردانند به متد async با نوع بازگشتی Task تبدیل خواهند شد.
متدهای async که void برمیگردانند هدفی خاص که آن "مدیریتهای خطای غیرهمزمان" (asynchronous error handlers) است را دارند، اما این متدها سینتکسی متفاوت برای مدیریت خطا دارند. هنگامی که استثنا در یک async Task یا <async Task <T پرتاب میشود، استثنا گرفته شده و مستقیماً در شیء Task قرار داده میشود. با یک متد async void، هیچ شیای از نوع Task درگیر نخواهد شد، بنابراین هر استثنای پرتاب شدهای در متد async void مستقیماً روی SynchronizationContextای که در زمان فراخوانی async void فعال است میآید. برای اطلاعات بیشتر به این مقالهی MSDN که شامل مثالهای خوبی است مراجعه کنید.
جمع بندی
در این مثال ساده ما از متد ()Task.Run برای آمیختن کلیدواژه await در کد استفاده کردیم، اما NET framework. تعداد زیادی API سازگار یا async فراهم میکند که از آن میتوانید برای استفاده با async await نیز استفاده کنید. این اعضا را میتوانید با شناسایی پسوند "Async" که به نام هر عضو پیوسته شده است و مقدار بازگشتی Task یا <Task <T تشخیص دهید. برای مثال کلاس System.IO.Stream شامل متدهایی ازقبیل CopyToAsync، ReadAsync و WriteAsync در کنار متدهای همزمان CopyTo، Read و Write است.
این مقاله مثالی ساده بود که امیدوارم شما را در درک نحوه استفاده از async و await کمک کند. این قابلیتی انعطافپذیر و قدرتمند در 5 #C و بالاتر است که نوشتن نرمافزارهای پاسخگو را آسانتر میکند.
ترجمه شده از وبسایت مایکرو سافت
کپی تنها با ذکر منبع مجاز است.
لطفا کد رو اصلاح کنید :