چیزهایی که باید در مورد Asyn/Await بدانید

سه شنبه 17 آذر 1394

در این مقاله راجع به کلمات کلیدی async/await صحبت خواهیم کرد.در نسخه های قبلی .NET با کمک چند نخی می توانستیم الگوی Asynchronous یا غیر همزمان را برای اینکه نرم افزاری پاسخگوتر داشته باشیم به کار گیریم .در نسخه های جدیدتر .NET با کمک دو کلمه کلیدی async و Await ایجاد برنامه های غیرهمزمان راحت تر شده است .

چیزهایی که باید در مورد Asyn/Await بدانید

Asynchronous Programming Modelیا APM چیست ؟این مدل  در جاهایی که عملیات غیر همزمان نیاز به یک شروع و پایان دارند ،کاربرد دارد به نام الگوی IAsyncResult  هم شناخته می شود.در روش همزمان هر عملی پس از اتمام عمل قبلی شروع به اجرا می کند .مثلا عمل دریافت اطلاعات از اینترنت را در نظر بگیرید این عمل به صورت همزمان انجام می شود چون Thread جاری را قفل کرده و تا پایان دانلود اطلاعات منتظر می ماند .این روش ، روش خوبی نیست زیرا کل UI کاربر در اینجا قفل شده و منتظر اتمام عملیات است .پیام معروف Not responding که گاهی آن را می بینید به دلیل قفل شدن سیستم است .در این حالت حتی اگر برنامه ما دارای چندین Thread باشد باز هم در کارایی برنامه تاثیری ندارد .چون مدام این Thread ها به بن بست یا blocking می خورند.می توان برای حل مشکل از Asynchronous API استفاده کرد اما اینکه برای رفع خطاهای احتمالی و همچنین برای دریافت نتیجه از این عملیات ناهمزمان چگونه باید عمل کرد خود مشکل دیگری است .

استفاده از روش synchronous (هم زمان) باعث می شود تا Thread اصلی برنامه که وظیفه بروز رسانی GUI را بر عهده دارد در زمان دانلود شدن فایل  قفل شود.

اما بکارگیری روش asynchronous یا غیرهم زمان ، باعث می شود تا عملیات دانلود در پس زمینه انجام شود و از Freeze شدن UI برنامه جلوگیری شود.

در نسخه های قبلی .NET با کمک چند نخی می توانستیم  الگوی Asynchronous یا غیر همزمان را برای اینکه نرم افزاری پاسخگو تر داشته با شیم به کار گیریم .در نسخه های جدیدتر .NET با کمک دو کلمه کلیدی async و Await ایجاد برنامه های غیرهمزمان راحت تر شده است .

اگر در تعریف تابعی از کلمه کلیدی Async استفاده کرده باشید این تابع می تواند از فراخوانی Await استفاده کند و به حالت تعلیق درآید.این فراخوانی به کامپایلر اعلام می کند که این تابع که در تعریف آن از Async استفاده کرده ایم دیگر نباید پیشروی کند و در این نقطه باید متوقف شود.تا اینکه پردازش های دیگری که در صف انتظار هستند تمام شوند .البته توجه داشته باشید که Async می تواند شامل Await باشد و میتواند شامل نباشد.اگر متد Async شامل Await نباشد کامپایلر آنرا یک تابع همزمان خواهد شناخت .

در همان مثال دانلود فایل از اینترنت اگر این دانلود به صورت یک تابع Async تعریف نشود کل برنامه به حالت قفل خواهد رفت پس نباید آنرا به صورت یک تابع همزمان تعریف کرد.در پردازش غیرهمزمان نرم افزار می تواند به کارهای دیگر خود که ارتباطی با دانلود فایل ندارد ادامه دهد.به شکل زیر توجه کنید

به محض اینکه کامپایلر کلمه Await را می بیند بلافاصله به تابع فراخوان که تابع با کلمه کلیدی Async را فراخوانی کرده است باز می گردد بدون اینکه اجرای بقیه تابع Async را ادامه دهد.یعنی بدنه تابع تا قبل از کلمه Await به صورت غیرهمزمان اجرا شده و بعد از رسیدن به Await به تابع فراخوان برمی گردد.

مزایای استفاده از Await

از آنجایی که HTTP یک پروتکل اساسا ناهمزمان است استفاده از Async و Await باعث می شود سریعا با این پروتکل Mach شویم .در عملیاتی که نیاز به I/O دارند استفاده از چندین Thread کارایی کلی سیستم را افزایش می دهد اما اگر اعمال محاسباتی داشته باشید استفاده از Thread خیلی مطلوب نخواهد بود.

با کمک این دو کلمه کلیدی (Async-Await) می توان عملیات را به صورت موازی(برای آشنایی بیشتر با پردازش های موازی کتاب فارسی برنامه نویسی موازی در سی شارپ را مطالعه بفرمایید) پردازش کرد سریعتر به نتیجه رسید .مزیت دیگر این روش این است که Thread های در حال کار سریعتر آزاد شده و به Thread Pool باز می گردند و می توانند در سایر پردازش ها مورد استفاده قرار گیرند.به شکل زیر توجه کنید

زمانی که منتظر اجرای Fn2 هستید این تابع به تابع f در یک Thread دیگر منتقل میشودو قرار است که Fn2 زمانی که f انرا فراخوانی می کند اجرا شود.

کنترل اجرا به تابع Fn1 باز میگردد و به اجرای آن می پردازد تا زمانی که بلاک wait برسد.

به محض اینکه تابع Async در Context b کامل میشود تلاش می کند که به Context a دسترسی پیدا کند

اما Context a قبلا بلاک شده است .بن بست اتفاق افتاده است

دو راه حل برای مشکل بالا وجود دارد

از بلاک wait استفاده نکنیم و تماما از When استفاده کنیم .که بعد از انجام کاری ، کار بعدی بعد از آن انجام شود.

از wrapped content برای اجرای Context b استفاده کنیم .

برای پیاده سازی روش دوم نیاز است که از await استفاده کنیم .به کد زیر توجه کنید .

class Program
{
    static void Main(string[] args)
    {
        Task<string> t = GetFileStringAsync(file_path);

        // Do some other work:
        // ...

        try
        {
            Console.WriteLine(t.Result.Substring(0, Math.Min(500, t.Result.Length)));
        }
        catch (AggregateException ae)
        {
            Console.WriteLine(ae.InnerException.Message);
        }

        Console.ReadKey();
    }

    const int MAX_FILE_SIZE = 14000000;
    public static Task<string> GetFileStringAsync(string path)
    {
        FileInfo fi = new FileInfo(path);
        byte[] data = null;
        data = new byte[fi.Length];

        FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, 
                                        FileShare.Read, data.Length, true);

        //Task<int> returns the number of bytes read
        Task<int> task = Task<int>.Factory.FromAsync(fs.BeginRead, fs.EndRead, 
                                        data, 0, data.Length, null);

        // It is possible to do other work here while waiting
        // for the antecedent task to complete.
        // ...

        // Add the continuation, which returns a Task<string>. 
        return task.ContinueWith((antecedent) =>
        {
            fs.Close();

            // If we did not receive the entire file, the end of the
            // data buffer will contain garbage.
            if (antecedent.Result < data.Length)
                Array.Resize(ref data, antecedent.Result);

            // Will be returned in the Result property of the Task<string>
            // at some future point after the asynchronous file I/O operation completes.
            return new UTF8Encoding().GetString(data);
        });
    }
}

در نمونه ای که ضمیمه این مقاله است در داخل برنامه اصلی کد های زیر نوشته شده است

  static void Main(string[] args)
        {
            Console.WriteLine("Sample Program Started");
            RunSample(1);
            Console.WriteLine("MyTaskTest1 done, waiting for async result...");
            Console.WriteLine("Whenever yoy saw the results press any key...");
            Console.ReadKey();

            RunSample(2);
            Console.WriteLine("MyTaskTest2.doTheJob() done, waiting for async result...");
            Console.WriteLine("Whenever yoy saw the results press any key...");
            Console.ReadKey();

            RunSample(3);
            Console.WriteLine("MyTaskTest2.doTheJobEx() done, waiting for async result...");
            Console.WriteLine("Whenever yoy saw the results press any key to exit.");
            Console.ReadKey();
        }

        static async void RunSample(int stateToTest)
        {
            switch (stateToTest)
            {
                case 1:
                    Console.WriteLine("Starting MyTaskTest1...");
                    MyTaskTest1 taskTest1 = new MyTaskTest1();
                    await taskTest1.doTheJob();
                    break;

                case 2:
                    Console.WriteLine("Starting MyTaskTest2...");
                    MyTaskTest2 taskTest2 = new MyTaskTest2();
                    await taskTest2.doTheJob();
                    break;

                case 3:
                    Console.WriteLine("Starting MyTaskTest2 another way...");
                    taskTest2 = new MyTaskTest2();
                    await taskTest2.doTheJobEx();
                    break;
            }
        }

پس از اجرا شکل زیر را خواهید دید . برنامه در خط RunSample  متوقف شده و به اجرای برنامه دیگری می پردازد این تابع یک پارامتر می گیرد که در واقع شماره برنامه می باشد.شکل زیر اجرای این پروژه را پس از اجرای Sample 1 نشان میدهد

آموزش سی شارپ

فایل های ضمیمه

برنامه نویسان

نویسنده 3207 مقاله در برنامه نویسان
  • C#.net
  • 10k بازدید
  • 11 تشکر

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید