آموزش استفاده از async و await در زبان سی شارپ

async and await in csharp 4907 آموزش استفاده از async و await در زبان سی شارپ

آموزش استفاده از async و await در زبان سی شارپ

شما می توانید با استفاده از برنامه نویسی ناهمگام میزان واکنش گرایی و سرعت نرم افزار های خود را به میزان قابل توجهی افزایش دهید. تکنیک های برنامه نویسی همگام و سنتی باعث می شود تا کارکرد نرم افزار پیچیده تر شود و این موضوع اشکال زدایی آن را سخت تر می کند. در نسخه ۵ زبان برنامه نویسی C# قابلیت async و await برای برنامه نویسی ناهمگام معرفی شد. در این مقاله آموزش استفاده از async و await در زبان سی شارپ را با استفاده از چند مثال ساده اما کاربردی به شما آموزش خواهیم داد.

بهبود پاسخگویی نرم افزار با async

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

جدول زیر بخش هایی را نشان می دهد که استفاده از async در آن ها باعث بهبود پاسخگویی می شود.

کاربرد کلاس های .Net با متدهای async کلاس های Windows Runtime با متدهای async
دسترسی به وب HttpClient SyndicationClient
کار با فایل ها StreamWriter, StreamReader, XmlReader StorageFile
کار با تصاویر MediaCapture, BitmapEncoder, BitmapDecoder
برنامه نویسی WCF Synchronous and Asynchronous Operations

رویکرد برنامه نویسی ناهمگام برای برنامه هایی که دارای UI هستند، بسیار مهم است. فعالیت های مرتبط با UI معمولا در یک thread به اشتراک گذاشته می شوند. اگر یک فعالیت در برنامه نویسی همگام قفل شود، باعث قفل شدن کل فعالیت ها می شود. هنگامی که از برنامه نویسی ناهمگام استفاده کنید، در حین انجام فعالیت های مختلف برنامه شما هنگ نمی کند و می توانید چند کار را باهم انجام دهید.

متدهای async

کلمه کلیدی async و await قلب برنامه نویسی ناهمگام هستند. با استفاده از این دو کلمه کلیدی می توانید از منابع موجود در Windows Runtime، .Net Framework و .Net Core برای ایجاد متدهای async استفاده کنید. نحوه ایجاد متدهای async تقریبا مانند متدهای عادی می باشد با این تفاوت که قبل از نوع باز گشتی متد از کلمه کلیدی async استفاده می شود.

مثال

مثال زیر زیر نحوه پیاده سازی و استفاده از یک متد async در سی شارپ را نشان می دهد. قسمت های مختلف کد با استفاده از کامنت توضیح داده شده است.

async Task<int> AccessTheWebAsync()
{
    HttpClient client = new HttpClient();
    Task<string> getStringTask = client.GetStringAsync("https://sourcesara.com ");
    DoIndependentWork();
    string urlContents = await getStringTask;
    return urlContents.Length;
}

توضیحات مثال

سه مورد مهمی که در الگوی متد وجود دارد:

  • کلمه کلیدی async قبل از نوع بازگشتی متد.
  • نوع بازگشتی از نوع Task که در اینجا به صورت Task<int> می باشد زیرا خروجی متد از یک عدد صحیح است.
  • نام متد که با کلمه Async خاتمه یافته است.

خروجی متد GetStringAsync یک Task<string>است بنابراین متغیری که خروجی این تابع در آن ذخیره می شود نیز باید از همین نوع باشد. متد DoIndependentWork() کاری را انجام می دهد که به نتیجه بازگشتی از متد GetStringAsync وابسته نیست.

عملگر await اجرای متد AccessTheWebAsync() را معلق می کند و این متد تا زمانی که getStringTask کامل نشده باشد، متوقف می ماند. در همین حال کنترل اجرای برنامه به متدی که AccessTheWebAsync() را فراخوانی کرده است باز می گردد. عملگر await نتیجه Task را از متغیر getStringTask بیرون می کشد و زمانی که عملیات getStringTask کامل شود، کنترل اجرای برنامه به این قسمت باز می گردد تا اجرای برنامه ادامه می یابد.

در آخر دستور return یک عدد صحیح را باز میگرداند و هر متدی که AccessTheWebAsync() را با استفاده از عملگر await فراخوانی کند، این عدد صحیح را می گرد. اگر متد AccessTheWebAsync() کاری به جز دسترسی به وب را انجام ندهد، می توانید کد بالا بدون  را به این صورت نیز بنویسید:

async Task<int> AccessTheWebAsync()
{
    HttpClient client = new HttpClient();
    string urlContents = await client.GetStringAsync("https://sourcesara.com ");
    return urlContents.Length;
}

نحوه کار متدهای async

شکل زیر نحوه کار متد async مثال بالا را نشان می دهد:

async and await in csharp 4907 1 آموزش استفاده از async و await در زبان سی شارپ

توضیحات عکس بالا بر اسا شماره هر قسمت:

  1. یک رویداد که متد AccessTheWebAsync را فراخوانی کرده و با استفاده از عملگر await منتظر پایان کار این متد است.
  2. در داخل متد AccessTheWebAsync یک نمونه از HttpClient ایجاد شده و محتوای سایت با فراخوانی متد GetStringAsync دانلود می شود.
  3. فعالیتی که در داخل متد GetStringAsync انجام می شود، باعث معلق شدن فرآیند اجرای متد می شود. ممکن است منتظر ماندن برای اتمام این فرآیند باعث قفل شده منابع شود. برای جلوگیری از بروز این مشکل متد GetStringAsync کنترل اجرای برنامه را به متدی که آن را فراخوانی کرده است باز می گرداند. متد GetStringAsync یک Task<TResult> باز می گرداند که در مثال بالا Task<string> است. مقدار بازگشتی از این متد در متغیر getStringTask ذخیره می شود.
  4. از آنجا که getStringTask هنوز با عملگر await اجرا نشده است، می توان کار دیگری که به نتیجه بازگشتی متد GetStringAsync وابسته نیست را انجام داد. در مثال بالا متد DoIndependentWork این کار را انجام می دهد.
  5. DoIndependentWork یک متد عادی (synchronous) است و بعد از انجام کار خود به متدی که آن را فراخوانی کرده است باز می گردد.
  6. کنترل اجرای برنامه به متدی که AccessTheWebAsync را فراخوانی کرده است باز می گردد و فعالیت هایی که به نتیجه getStringTask وابسته نیستند را انجام می دهد. زمانی که اجرای Task پایان یابد کنترل اجرای برنامه به AccessTheWebAsync باز می گردد و سایر کدها را اجرا می کند.
  7. رشته موجود در getStringTask (تولید شده توسط متد GetStringAsync) توسط عملگر await گرفته شده و در متغیر urlContents ذخیره می گردد.
  8. حال متد AccessTheWebAsync محتوای سایت را دارد و می تواند طول آن را محاسبه کرده و به عنوان خروجی باز گرداند. سپس کار متد AccessTheWebAsync کامل می شود و برنامه می تواند کار خود را ادامه دهد.

API async methods

ممکن است از خود بپرسید که متدهایی نظیر GetStringAsync که از برنامه نویسی ناهمگام (Asynchronous) پشتیبانی می کنند را چگونه باید پیدا کنم! فریم ورک .Net 4.5 و بالا تر و همچنین .Net Core شامل متدهای زیادی هستند که با استفاده از async و await پیاده سازی شده اند. شما می توانید با پسوند Async موجود در نام این متدها و همچنین نوع خروجی که معمولا Task و Task<TResult> است، آن ها را تشخیص دهید. برای مثال کلاس System.IO.Stream در کنار متدهای synchronous (مانند CopyTo، Read و Write) متدهای Asynchronous (مانند CopyToAsync، ReadAsync و WriteAsync) را نیز شامل می شود.

نخ ها (Threads)

متدهای async با هدف انجام عملیات های مختلف بدون قفل کردن منابع ایجاد شده اند. دستور await در یک متد async در زمان انجام فعالیت مورد نظر، نخ (Thread) جاری را قفل نمی کند. توجه داشته باشید که کلمات کلیدی async و await باعث بوجود آمدن یک نخ جدید نمی شوند.

async و await

زمانی که شما یک متد را با استفاده از کلمه کلیدی async به یک متد Asynchronous تبدیل می کنید، دو قابلیت زیر در آن متد فعال می شوند:

  • در متد async می توان از عملگر await استفاده کرد. این عملگر به کامپایلر می گوید تا زمانی که عملیات ناهمگام به پایان نرسد نمی تواند کد ها بعد از آن را اجرا کند و کنترل به متد فراخوانی کننده باز گردانده می شود.
  • اجرای متدی ناهمگامی که توسط عملگر await معلق شده است، تا زمان پایان یافتن عملیات مورد نظر ادامه پیدا نمی کند.

به طور عادی در متدهای async یک یا چند عملیات با استفاده از عملگر await انجام می شود. با این حال عدم استفاده از عملگر wait در متدهای async باعث بروز خطا نمی شود ولی کامپایلر در مورد آن هشدار می دهد.

انواع بازگشتی و پارامترها

در حالت عادی خروجی متدهای asynchronous به صورت Task یا Task<TResult> می باشد. در مواقعی که متد شما یک مقدار به عنوان خروجی باز میگرداند (مثلا یک رشته) باید از Task<TResult> استفاده کنید. و زمانی که متد شما هیچ مقدار بازگشتی ندارد یعنی خروجی آن void است باید از Task استفاده کنید.

مثال زیر نحوه اعلان و فراخوانی متدی که خروجی آن از نوع Task یا Task<TResult> می باشد را نشان می دهد.

// خروجی متد از نوع Task<TResult>
async Task<int> TaskOfTResult_MethodAsync()
{
	int hours;
	// . . .
	// بازگرداندن یک عدد صحیح به عنوان خروجی.
	return hours;
}
// فراخوانی متد  TaskOfTResult_MethodAsync بدون عملگر await
Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();
// گرفتن خروجی ذخیره شده در متغیر بالا با عملگر await
int intResult = await returnedTaskTResult;
// ،فراخوانی متد بالا با استفاده از عملگر await و گرفتن خروجی آن
int intResult = await TaskOfTResult_MethodAsync();
// خروجی متد از نوع Task
async Task Task_MethodAsync()
{
	// . . .
	// این متد هیچ خروجی ندارد.
}
// فراخوانی متد بالا بدون عملگر await
Task returnedTask = Task_MethodAsync();
await returnedTask;
// فراخوانی متد بالا به عملگر await

خروجی یک متد async می تواند به صورت void باشد. این نوع خروجی در event handler ها استفاده می شود.

نکته! متد های async که خروجی آن ها به صورت void می باشد را نمی توان با عملگر await فراخوانی کرد.

نکته! پارامترهای متد async نمی توانند به صورت in، out و ref تعریف شوند.

خروجی های زیر مربوط به متدهای async در Windows Runtime هستند:

  • IAsyncOperation<TResult> که مطابق با Task<TResult> می باشد.
  • IAsyncAction که مطابق با Task می باشد.
  • IAsyncActionWithProgress<TProgress>
  • IAsyncOperationWithProgress<TResult, TProgress>

قوانین نامگذاری متدهای async

در زبان برنامه نویسی سی شارپ بر اساس قوانین نام گذاری، متد هایی که به صورت async باشند، باید در انتهای نام خود از کلمه Async استفاده کنند. این قوانین برای متد ها استفاده می شود و نمی توان در نام گذاری کلاس ها، اینترفیس ها و Event Handler ها از آن استفاده کرد.

مثال کامل

در مثال زیر محتوای تعدادی سایت را با استفاده از کلاس WebClient دانلود می کنیم و سپس طول محتوای هر کدام به همراه میزان زمان سپری شده برای دانلود شدن محتوای همه سایت ها را نشان می دهیم. ظاهر مثال شامل سه عدد Button با نام های BtnSync، BtnAsync و BtnParallelAsync و یک عدد TextBlock در WPF و RichTextBox در WinForms با نام TxtLogs برای نمایش نتایج می باشد.

async and await in csharp 4907 2 آموزش استفاده از async و await در زبان سی شارپ

کد مربوط به نسخه WPF این مثال:

محتوای فایل MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Threading.Tasks;
using System.Windows;

namespace WpfExample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow
    {
        #region Fields

        /// <summary>
        /// لیست سایت هایی که باید دانلود شوند
        /// </summary>
        private readonly IEnumerable<string> _sites;

        #endregion

        #region Constructor

        public MainWindow()
        {
            InitializeComponent();

            _sites = new List<string>
            {
                "https://www.google.com/",
                "https://www.yahoo.com/",
                "https://www.bing.com/",
                "https://www.dropbox.com/",
                "https://stackoverflow.com/",
                "https://sourcesara.com/",
                "https://www.instagram.com"
            };
        }

        #endregion

        #region Methods

        /// <summary>
        /// ثبت اطلاعات سایت دانلود شده در قسمت log
        /// </summary>
        /// <param name="data">اطلاعات سایت دانلود شده</param>
        private void ReportResult(DownloadedSiteViewModel data)
        {
            TxtLogs.Text += $"[{data.Url}] Downloaded," +
                            " Content Length is " +
                            $"[{int.Parse(data.Length) / 1024} KB]\r\n";
        }

        /// <summary>
        /// نمایش زمانی سپری شده برای دانلود سایت ها
        /// </summary>
        /// <param name="time">زمان بر حسب ثانیه</param>
        private void LogTime(string time)
        {
            TxtLogs.Text += $"Elapsed time to downloading websites [{time} Second]\r\n";
        }

        /// <summary>
        /// دانلود سایت به صورت عادی
        /// </summary>
        /// <param name="url">آدرس سایتی که باید دانلود شود</param>
        /// <returns>DownloadedSiteViewModel</returns>
        private static DownloadedSiteViewModel DownloadSite(string url)
        {
            try
            {
                var data = new DownloadedSiteViewModel();
                using (var client = new WebClient())
                {
                    data.Length = client.DownloadString(url).Length.ToString();
                    data.Url = url;
                }
                return data;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                return null;
            }
        }

        /// <summary>
        /// دانلود سایت به صورت ناهمگام
        /// </summary>
        /// <param name="url">آدرس سایتی که باید دانلود شود</param>
        /// <returns>Task</returns>
        private static async Task<DownloadedSiteViewModel> DownloadSiteAsync(string url)
        {
            try
            {
                var data = new DownloadedSiteViewModel();
                using (var client = new WebClient())
                {
                    var siteData = await client.DownloadStringTaskAsync(url);
                    data.Length = siteData.Length.ToString();
                    data.Url = url;
                }
                return data;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                return null;
            }
        }

        /// <summary>
        /// دانلود سایت های موجود در لیست با استفاده از متد DownloadSite و به صورت عادی
        /// </summary>
        private void BeginDownload()
        {
            foreach (var site in _sites)
            {
                var result = DownloadSite(site);
                ReportResult(result);
            }
        }

        /// <summary>
        /// دانلود سایت های موجود در لیست با استفاده از متد DownloadSiteAsync و به صورت ناهمگام
        /// </summary>
        /// <returns>Task</returns>
        private async Task BeginDownloadAsync()
        {
            foreach (var site in _sites)
            {
                var result = await DownloadSiteAsync(site);
                ReportResult(result);
            }
        }

        /// <summary>
        /// دانلود سایت های موجود در لیست با استفاده از متد DownloadSiteAsync و به صورت ناهمگام و موازی
        /// </summary>
        /// <returns>Task</returns>
        private async Task BeginParallelDownloadAsync()
        {
            // ایجاد یک لیست برای نگهداری task ها
            var tasks = new List<Task<DownloadedSiteViewModel>>();

            // افزودن task به لیست بالا
            foreach (var site in _sites)
                tasks.Add(DownloadSiteAsync(site));

            // این تابع منتظر می ماند تا همه task های موجود در لیست به طور کامل انجام شوند
            var results = await Task.WhenAll(tasks);

            // نمایش نتیجه
            foreach (var result in results)
                ReportResult(result);
        }

        #endregion

        #region UI Events

        private void BtnSync_OnClick(object sender, RoutedEventArgs e)
        {
            // پاک کردن log های قبلی
            TxtLogs.Text = null;
            // ایجاد یک Stopwatch برای بدست آوردن زمان سپری شده
            var stopWatch = Stopwatch.StartNew();
            // فراخوانی متد
            BeginDownload();
            // متوقف کردن StopWatch
            stopWatch.Stop();
            // نمایش زمان سپری شده
            LogTime((stopWatch.ElapsedMilliseconds / 1000).ToString());
        }

        private async void BtnAsync_OnClick(object sender, RoutedEventArgs e)
        {
            // پاک کردن log های قبلی
            TxtLogs.Text = null;
            // ایجاد یک Stopwatch برای بدست آوردن زمان سپری شده
            var stopWatch = Stopwatch.StartNew();
            // فراخوانی متد
            await BeginDownloadAsync();
            // متوقف کردن StopWatch
            stopWatch.Stop();
            // نمایش زمان سپری شده
            LogTime((stopWatch.ElapsedMilliseconds / 1000).ToString());
        }

        private async void BtnParallelAsync_OnClick(object sender, RoutedEventArgs e)
        {
            // پاک کردن log های قبلی
            TxtLogs.Text = null;
            // ایجاد یک Stopwatch برای بدست آوردن زمان سپری شده
            var stopWatch = Stopwatch.StartNew();
            // فراخوانی متد
            await BeginParallelDownloadAsync();
            // متوقف کردن StopWatch
            stopWatch.Stop();
            // نمایش زمان سپری شده
            LogTime((stopWatch.ElapsedMilliseconds / 1000).ToString());
        }

        #endregion
    }
}

کد مربوط به نسخه WinForms این مثال:

محتوای فایل Form1.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WinFormsExample
{
    public partial class Form1 : Form
    {
        #region Fields

        /// <summary>
        /// لیست سایت هایی که باید دانلود شوند
        /// </summary>
        private readonly IEnumerable<string> _sites;

        #endregion

        #region Constructor

        public Form1()
        {
            InitializeComponent();

            _sites = new List<string>
            {
                "https://www.google.com/",
                "https://www.yahoo.com/",
                "https://www.bing.com/",
                "https://www.dropbox.com/",
                "https://stackoverflow.com/",
                "https://sourcesara.com/",
                "https://www.instagram.com"
            };
        }

        #endregion

        #region Methods

        /// <summary>
        /// ثبت اطلاعات سایت دانلود شده در قسمت log
        /// </summary>
        /// <param name="data">اطلاعات سایت دانلود شده</param>
        private void ReportResult(DownloadedSiteViewModel data)
        {
            TxtLogs.Text += $"[{data.Url}] Downloaded," +
                            " Content Length is " +
                            $"[{int.Parse(data.Length) / 1024} KB]\r\n";
        }

        /// <summary>
        /// نمایش زمانی سپری شده برای دانلود سایت ها
        /// </summary>
        /// <param name="time">زمان بر حسب ثانیه</param>
        private void LogTime(string time)
        {
            TxtLogs.Text += $"Elapsed time to downloading websites [{time} Second]\r\n";
        }

        /// <summary>
        /// دانلود سایت به صورت عادی
        /// </summary>
        /// <param name="url">آدرس سایتی که باید دانلود شود</param>
        /// <returns>DownloadedSiteViewModel</returns>
        private static DownloadedSiteViewModel DownloadSite(string url)
        {
            try
            {
                var data = new DownloadedSiteViewModel();
                using (var client = new WebClient())
                {
                    data.Length = client.DownloadString(url).Length.ToString();
                    data.Url = url;
                }
                return data;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                return null;
            }
        }

        /// <summary>
        /// دانلود سایت به صورت ناهمگام
        /// </summary>
        /// <param name="url">آدرس سایتی که باید دانلود شود</param>
        /// <returns>Task</returns>
        private static async Task<DownloadedSiteViewModel> DownloadSiteAsync(string url)
        {
            try
            {
                var data = new DownloadedSiteViewModel();
                using (var client = new WebClient())
                {
                    var siteData = await client.DownloadStringTaskAsync(url);
                    data.Length = siteData.Length.ToString();
                    data.Url = url;
                }
                return data;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                return null;
            }
        }

        /// <summary>
        /// دانلود سایت های موجود در لیست با استفاده از متد DownloadSite و به صورت عادی
        /// </summary>
        private void BeginDownload()
        {
            foreach (var site in _sites)
            {
                var result = DownloadSite(site);
                ReportResult(result);
            }
        }

        /// <summary>
        /// دانلود سایت های موجود در لیست با استفاده از متد DownloadSiteAsync و به صورت ناهمگام
        /// </summary>
        /// <returns>Task</returns>
        private async Task BeginDownloadAsync()
        {
            foreach (var site in _sites)
            {
                var result = await DownloadSiteAsync(site);
                ReportResult(result);
            }
        }

        /// <summary>
        /// دانلود سایت های موجود در لیست با استفاده از متد DownloadSiteAsync و به صورت ناهمگام و موازی
        /// </summary>
        /// <returns>Task</returns>
        private async Task BeginParallelDownloadAsync()
        {
            // ایجاد یک لیست برای نگهداری task ها
            var tasks = new List<Task<DownloadedSiteViewModel>>();

            // افزودن task به لیست بالا
            foreach (var site in _sites)
                tasks.Add(DownloadSiteAsync(site));

            // این تابع منتظر می ماند تا همه task های موجود در لیست به طور کامل انجام شوند
            var results = await Task.WhenAll(tasks);

            // نمایش نتیجه
            foreach (var result in results)
                ReportResult(result);
        }

        #endregion

        #region UI Events

        private void BtnSync_Click(object sender, EventArgs e)
        {
            // پاک کردن log های قبلی
            TxtLogs.Text = null;
            // ایجاد یک Stopwatch برای بدست آوردن زمان سپری شده
            var stopWatch = Stopwatch.StartNew();
            // فراخوانی متد
            BeginDownload();
            // متوقف کردن StopWatch
            stopWatch.Stop();
            // نمایش زمان سپری شده
            LogTime((stopWatch.ElapsedMilliseconds / 1000).ToString());
        }

        private async void BtnAsync_Click(object sender, EventArgs e)
        {
            // پاک کردن log های قبلی
            TxtLogs.Text = null;
            // ایجاد یک Stopwatch برای بدست آوردن زمان سپری شده
            var stopWatch = Stopwatch.StartNew();
            // فراخوانی متد
            await BeginDownloadAsync();
            // متوقف کردن StopWatch
            stopWatch.Stop();
            // نمایش زمان سپری شده
            LogTime((stopWatch.ElapsedMilliseconds / 1000).ToString());
        }

        private async void BtnParallelAsync_Click(object sender, EventArgs e)
        {
            // پاک کردن log های قبلی
            TxtLogs.Text = null;
            // ایجاد یک Stopwatch برای بدست آوردن زمان سپری شده
            var stopWatch = Stopwatch.StartNew();
            // فراخوانی متد
            await BeginParallelDownloadAsync();
            // متوقف کردن StopWatch
            stopWatch.Stop();
            // نمایش زمان سپری شده
            LogTime((stopWatch.ElapsedMilliseconds / 1000).ToString());
        }

        #endregion
    }
}

نوشته آموزش استفاده از async و await در زبان سی شارپ اولین بار در سورس سرا - آموزش برنامه نویسی. پدیدار شد.

درباره نویسنده: administrator

ممکن است دوست داشته باشید

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *