C#异步:等待
await关键字可以简便地附加延续。
static void Main(string[] args) { DisplayPrimesCount(); } static async void DisplayPrimesCount() { int result = await GetPrimesCountAsync(2, 1000000); Console.WriteLine(result); } /// <summary> /// 获得素数个数 /// </summary> /// <param name="start">从什么数开始</param> /// <param name="count">要获取的连续整数的数目</param> /// <returns></returns> static Task<int> GetPrimesCountAsync(int start, int count) { return Task.Run(() =>ParallelEnumerable.Range(start, count) .Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0))); }
添加了async修饰符的方法称为异步函数。
当遇到await表达式时,通常情况下执行过程会返回到调用者上。运行时再返回之前会在等待的任务上附加一个延续,保证任务结束时执行点会跳回到方法中,并继续剩余的代码。如果顺利结束,则返回值为await表达式赋值。
可以以下例子展开形式印证:
static void DisplayPrimesCount() { var awaiter = GetPrimesCountAsync(2, 1000000).GetAwaiter() ; awaiter.OnCompleted(()=> { int result = awaiter.GetResult(); Console.WriteLine(result); }); }
await等待的表达式通常情况下是一个任务。但实际上,只要该对象拥有GetAwaiter
方法,且该方法的返回值为等待器,则编译器都可以接受。
await表达式的最大优势在于它几乎可以出现在任何异步函数的表达式中,但不能出现在lock或unsafe上下文中。
以下示例中,await出现在循环结构中:
static async void DisplayPrimesCounts() { for (int i = 0; i < 10; i++) { Console.WriteLine(await GetPrimesCountAsync(i * 1000000 + 2, 1000000)); } }
在第一次执行GetPrimesCountAsync方法时,由于出现了await表达式,因此执行点返回给调用者。当方法完成时,执行点会从停止之处恢复执行,同时保留局部变量和循环计数器的值。
UI上的等待处理
现在将编写一个UI程序,并且使该程序在调用计算密集的方法时仍然保持UI的响应性。
首先示例同步实现:
public partial class TestUI : Window { Button _button = new Button { Content = "Go" }; TextBlock _results = new TextBlock(); public TestUI() { InitializeComponent(); var panel = new StackPanel(); panel.Children.Add(_button); panel.Children.Add(_results); Content = panel; _button.Click += (sender, args) => Go(); } void Go() { for (int i = 1; i < 5; i++) { _results.Text += GetPrimesCount(i * 1000000, 1000000) + " primes between " + (i * 1000000) + " and " + ((i + 1) * 1000000 - 1) + Environment.NewLine; } } int GetPrimesCount(int start, int count) { return ParallelEnumerable.Range(start, count) .Count(n =>Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)); } }
当点击按钮时,由于执行计算密集代码时间长,程序会陷入无响应状态。
我们可以分两步实现相应的异步方法。
第一步:异步的GetPrimesCount方法
Task<int> GetPrimesCountAsync (int start, int count) { return Task.Run (() => ParallelEnumerable.Range (start, count).Count (n => Enumerable.Range (2, (int) Math.Sqrt(n)-1).All (i => n % i > 0))); }
第二步:在Go方法中调用
async void Go() { _button.IsEnabled = false; for (int i = 1; i < 5; i++) _results.Text += await GetPrimesCountAsync (i * 1000000, 1000000) + " primes between " + (i*1000000) + " and " + ((i+1)*1000000-1) + Environment.NewLine; _button.IsEnabled = true; }
由以上代码可见异步函数的简洁性,只需按同步方式书写,并当调用异步函数时进行等待await就可以避免阻塞了。
GetPrimesCountAsync方法会运行在工作线程上,而Go方法则会“租用”UI线程时间,即Go方法在消息循环中是以伪并发的方式执行的。
本文来自博客园,作者:一纸年华,转载请注明原文链接:https://www.cnblogs.com/nullcodeworld/p/16412642.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?