آموزش استفاده از async و await در زبان سی شارپ
شما می توانید با استفاده از برنامه نویسی ناهمگام میزان واکنش گرایی و سرعت نرم افزار های خود را به میزان قابل توجهی افزایش دهید. تکنیک های برنامه نویسی همگام و سنتی باعث می شود تا کارکرد نرم افزار پیچیده تر شود و این موضوع اشکال زدایی آن را سخت تر می کند. در نسخه 5 زبان برنامه نویسی 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 در سی شارپ را نشان می دهد. قسمت های مختلف کد با استفاده از کامنت توضیح داده شده است.
1 2 3 4 5 6 7 8 | 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() کاری به جز دسترسی به وب را انجام ندهد، می توانید کد بالا را به این صورت نیز بنویسید:
1 2 3 4 5 6 | async Task<int> AccessTheWebAsync() { HttpClient client = new HttpClient(); string urlContents = await client.GetStringAsync("https://sourcesara.com "); return urlContents.Length; } |
نحوه کار متدهای async
شکل زیر نحوه کار متد async مثال بالا را نشان می دهد:
توضیحات عکس بالا بر اسا شماره هر قسمت:
- یک رویداد که متد AccessTheWebAsync را فراخوانی کرده و با استفاده از عملگر await منتظر پایان کار این متد است.
- در داخل متد AccessTheWebAsync یک نمونه از HttpClient ایجاد شده و محتوای سایت با فراخوانی متد GetStringAsync دانلود می شود.
- فعالیتی که در داخل متد GetStringAsync انجام می شود، باعث معلق شدن فرآیند اجرای متد می شود. ممکن است منتظر ماندن برای اتمام این فرآیند باعث قفل شده منابع شود. برای جلوگیری از بروز این مشکل متد GetStringAsync کنترل اجرای برنامه را به متدی که آن را فراخوانی کرده است باز می گرداند. متد GetStringAsync یک Task<TResult> باز می گرداند که در مثال بالا Task<string> است. مقدار بازگشتی از این متد در متغیر getStringTask ذخیره می شود.
- از آنجا که getStringTask هنوز با عملگر await اجرا نشده است، می توان کار دیگری که به نتیجه بازگشتی متد GetStringAsync وابسته نیست را انجام داد. در مثال بالا متد DoIndependentWork این کار را انجام می دهد.
- DoIndependentWork یک متد عادی (synchronous) است و بعد از انجام کار خود به متدی که آن را فراخوانی کرده است باز می گردد.
- کنترل اجرای برنامه به متدی که AccessTheWebAsync را فراخوانی کرده است باز می گردد و فعالیت هایی که به نتیجه getStringTask وابسته نیستند را انجام می دهد. زمانی که اجرای Task پایان یابد کنترل اجرای برنامه به AccessTheWebAsync باز می گردد و سایر کدها را اجرا می کند.
- رشته موجود در getStringTask (تولید شده توسط متد GetStringAsync) توسط عملگر await گرفته شده و در متغیر urlContents ذخیره می گردد.
- حال متد 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> می باشد را نشان می دهد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // خروجی متد از نوع 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 در 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 برای نمایش نتایج می باشد.
کد مربوط به نسخه WPF این مثال:
محتوای فایل MainWindow.xaml.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | 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="site">اطلاعات سایت دانلود شده</param> private void ReportResult(Site site) { TxtLogs.Text += $"[{site.Url}] Downloaded," + " Content Length is " + $"[{int.Parse(site.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></returns> private static Site DownloadSite(string url) { try { var site = new Site(); using (var client = new WebClient()) { site.Length = client.DownloadString(url).Length.ToString(); site.Url = url; } return site; } catch (Exception ex) { MessageBox.Show(ex.Message); return null; } } /// <summary> /// دانلود سایت به صورت ناهمگام /// </summary> /// <param name="url">آدرس سایتی که باید دانلود شود</param> /// <returns></returns> private static async Task<Site> DownloadSiteAsync(string url) { try { var site = new Site(); using (var client = new WebClient()) { var siteData = await client.DownloadStringTaskAsync(url); site.Length = siteData.Length.ToString(); site.Url = url; } return site; } 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></returns> private async Task BeginDownloadAsync() { foreach (var site in _sites) { var result = await DownloadSiteAsync(site); ReportResult(result); } } /// <summary> /// دانلود سایت های موجود در لیست با استفاده از متد DownloadSiteAsync و به صورت ناهمگام و موازی /// </summary> /// <returns></returns> private async Task BeginParallelDownloadAsync() { // ایجاد یک لیست برای نگهداری task ها var tasks = new List<Task<Site>>(); // افزودن 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | 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="site">اطلاعات سایت دانلود شده</param> private void ReportResult(Site site) { TxtLogs.Text += $"[{site.Url}] Downloaded," + " Content Length is " + $"[{int.Parse(site.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>Site</returns> private static Site DownloadSite(string url) { try { var site = new Site(); using (var client = new WebClient()) { site.Length = client.DownloadString(url).Length.ToString(); site.Url = url; } return site; } catch (Exception ex) { MessageBox.Show(ex.Message); return null; } } /// <summary> /// دانلود سایت به صورت ناهمگام /// </summary> /// <param name="url">آدرس سایتی که باید دانلود شود</param> /// <returns></returns> private static async Task<Site> DownloadSiteAsync(string url) { try { var site = new Site(); using (var client = new WebClient()) { var siteData = await client.DownloadStringTaskAsync(url); site.Length = siteData.Length.ToString(); site.Url = url; } return site; } 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<Site>>(); // افزودن 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 } } |
سوال : اگر ما دوتا تابع داشته باشیم که نوعشان Task await باشد و هردو را باهم فراخوانی کنیم.کانفیلیکتی در اجرا نمیدهد؟
سلام...اگه دو تابع وابستگی به هم نداشته باشن، مشکلی بوجود نمیاد.
سلام و با عرض احترام بنده مدت یک هفتس دارم تو کل سایت ها و اموزش های ایرانی و خارجی گشت میزنم اما هنوز واقعا نفهمیدم که با async و await به چه شکل برنامه موازی اجرا میشه؟ مگر جز اینه که تفاوت اصلی برنامه نویسی همگام و ناهمگام در اینه که در همگام کد ها به صورت معمولی و خط به خط اجرا میشن اما در موازی همه باهم اجرا میشن؟ در اینجا ما کلمه await رو داریم که میاد و برنامه رو روی اون متد نگه میداره تا عملیاتش تموم شه و در اون زمان هیچ کاری نمیکنه پس این چه تفاوتی با یک برنامه همگام معمولی داره که اونم تا به یک متد میرسه تا جوابش نیاد سراغ بعدی ها نمیره اگر میشه یک راهنمایی بکنید من خیلی تو این موضوع گیر کردم تنها تفاوتی که در این موضوع await دیدم اینه که عملیات رو در بکگراند اجرا میکنه تا ui قفل نشه ولی اصلا برنامه ای رو همزمان اجرا نمیکنه و منتظر جواب متدی که با await مشخص شده میمونه و بعد به خط بعد میره بازم با تشکر از سایت خوبتون
سلام....داخل انجمن سایت مطرح کنید...تا کسانی که میتونن کمکتون کنن.