استفاده از برنامه نویسی غیر همزمان به ما اجازه می دهد تا قابلیت پاسخگویی برنامه خود را افزایش دهیم. این مفهوم، در صورت داشتن پردازش های سنگین که باعث توقف عمکرد کل برنامه می شود، به ما کمک می کند. استفاده از یک فرآیند غیر همزمان به برنامه اجازه می دهد تا کار خود را بدون وابستگی به فعالیت های مسدود کننده، تا پایان کار ادامه دهد. در اینجا، ما به بررسی کلمات کلیدی "async" و "await" در سی شارپ می پردازیم.
یک Task، نشان دهنده یک عملیات غیر همزمان است. عملیاتی که می تواند در پس زمینه اجرا شود. taskها را می توانیم بصورت زنجیره، یکی پس از دیگری اجرا کنیم.
کدهای زیر را در نظر بگیرید.
private async void button1_Click(object sender, EventArgs e) { textBox1.Text = "Wait..." // ... DOING SOME OPERATIONS string result = await SimpleMethodAsync(); textBox1.Text = result; } private async Task<string> SimpleMethodAsync() { // ... DOING SOMETHING await Task.Delay(2000); return "Finished" }
همانطور که ملاحظه می کنید، در اینجا از سه کلمه کلیدی async ،await و task استفاده کردیم. از کلمه کلیدی async در متد button1_Click استفاده می کنیم. این نشان دهنده این است که ما یک متد غیر همزمان تعریف کرده ایم. بنابراین وقتی متد button1_Click صدا زده می شود و به کلمه کلیدی await می رسد، متد SimpleMethodAsync فراخوانی می شود و کارهای مستقل دیگر نیز می توانند انجام شوند. کلمه کلیدی await نشان می دهد که ما به نتیجه متد SimpleMethodAsync نیاز داریم اما نیازی به مسدود کردن برنامه نداریم.
در تعریف متد SimpleMethodAsync نیز از کلمه کلیدی async استفاده می کنیم. این متد یک string بر می گرداند که ما آن را با این نوع بازگشتی مشخص کرده ایم.
Task<string>
کلمه کلیدی async باعث فعال شدن کلمه کلیدی await در تابع می شود و نحوه مدیریت متد را تغییر می دهد. یک متد async تا زمانی که به کلمه کلیدی await برسد، مانند سایر متدها در یک زمان اجرا می شود.
از تصورات اشتباهی که وجود دارد، یکسان دانستن مفهوم Thread و Task و همچنین جایگزین دانستن Task می باشد. یک Task می تواند یک نتیجه را برگرداند، در حالی که هیچ مکانیسم مستقیمی برای بازگشت نتیجه از یک Thread وجود ندارد.
همانطور که می دانید، در وب سرور IIS به ازای هر کاربر، یک Thread به آن کاربر اختصاص پیدا می کند و به ازای هر Thread نیز توسط وب سرور مقدار حافظه ای اختصاص پیدا می کند و ممکن است با زیاد شدن کاربران، سرور با محدودیت حجم روبرو شود.
اما در مقابل Node.js به عنوان یک Backend، به ازای تمام کاربران یک Thread ایجاد می کند. در این شرایط می توانیم مفهوم async را بهتر درک کنیم. تصور کنید هفتاد نفر به طور همزمان به اپلیکیشن درخواست می دهند و روی یک thread باید هفتاد task به صورت async اجرا شود.
در Multithreading، تمام Threadها به طور مستقل از هم کار می کنند اما در Asynchronous، در همان یک Thread به نوعی تقسیم کار می شود و Thread جاری بین Taskها تقسیم می شود. به همین دلیل توصیه می شود که اکشن هایی که ممکن است دارای پردازش های سنگینی باشند را به صورت async تعریف کنیم.
در مثالی دیگر، سه متد را به صورت همزمان اجرا می کنیم.
class Program { static void Main(string[] args) { var sw = new Stopwatch(); sw.Start(); f1(); f2(); f3(); sw.Stop(); var elapsed = sw.ElapsedMilliseconds; Console.WriteLine($"elapsed: {elapsed} ms"); Console.ReadKey(); } static void f1() { Console.WriteLine("f1 called"); Thread.Sleep(4000); } static void f2() { Console.WriteLine("f2 called"); Thread.Sleep(7000); } static void f3() { Console.WriteLine("f3 called"); Thread.Sleep(2000); } }
از طریق Thread.Sleep، یک محاسبات طولانی را شبیه سازی می کنیم و از طریق Stopwatch، زمان اجرای متدها را اندازه گیری می کنیم. در این روش متدها به صورت متوالی صدا زده می شوند. در سیستم ما، اجرای سه عملکرد 13 ثانیه طول کشید.
حالا، مثال خود را از طریق async بازنویسی می کنیم.
class Program { static void Main(string[] args) { var sw = new Stopwatch(); sw.Start(); Task.WaitAll(f1(), f2(), f3()); sw.Stop(); var elapsed = sw.ElapsedMilliseconds; Console.WriteLine($"elapsed: {elapsed} ms"); Console.ReadKey(); } static async Task f1() { await Task.Delay(4000); Console.WriteLine("f1 finished"); } static async Task f2() { await Task.Delay(7000); Console.WriteLine("f2 finished"); } static async Task f3() { await Task.Delay(2000); Console.WriteLine("f3 finished"); } }
از طریق Task.WaitAll، منتظر تکمیل اجرای taskهای ارائه شده می مانیم.
اکنون زمان اجرای ما به هفت ثانیه کاهش پیدا کرد. توجه داشته باشید که ترتیب اجرای taskها متفاوت می باشد.
زمانی که بخواهیم از کلمه کلیدی await در بدنه متد Main استفاده کنیم، باید در تعریف آن از کلمه کلیدی async استفاده کنیم.
class Program { static async Task Main(string[] args) { var sw = new Stopwatch(); sw.Start(); Console.WriteLine("task 1"); Task task1 = doWork(); Console.WriteLine("task 2"); Task task2 = doWork(); Console.WriteLine("task 3"); Task task3 = doWork(); await Task.WhenAll(task1, task2, task3); Console.WriteLine("Tasks finished"); sw.Stop(); var elapsed = sw.ElapsedMilliseconds; Console.WriteLine($"elapsed: {elapsed} ms"); } static async Task doWork() { await Task.Delay(1500); } }
در داخل متد Main، سه بار doWork را صدا می زنیم.
مفهوم TPL (Task Parallel Library) یکی از جالب ترین ویژگی های جدید است که در نسخه های اخیر فریم ورک دات نت اضافه شده است. متدهای Task.WaitAll و Task.WhenAll دو روش مهم و پرکاربرد در TPL هستند.
در thread ,Task.WaitAll جاری تا لحظه تکمیل اجرای taskهای معرفی شده، متوقف می شود. در زمان استفاده از Task.WhenAll نیز تمام taskهای معرفی شده اجرا می شوند اما thread جاری برنامه متوقف نمی شود.
در اصل، Task.WhenAll به شما یک task را می دهد که کامل نیست، اما می توانید از ContinueWith به محض انجام کارهای مشخص شده استفاده کنید.
Task.WhenAll(taskList).ContinueWith(t => { // write your code here });
پایان