当前位置: 首页 > news >正文

C# async、await异步语法 +Async+Task实现斐波那契数列渲染案例

笔记承接:承接Task全套笔记,是多线程最后一节核心语法;Async+Await是C#语法糖,简化异步代码、解决回调地狱,是目前异步编程最优写法

前置学习链路:Thread原生线程→ThreadPool线程池→Task异步任务→async/await语法

窗体控件:button1~button4,分别测试异步执行、返回值、串行任务

二、async与await基础语法(必考)

2.1 两个关键字作用

  • async:修饰方法,标识该方法为异步方法;单独使用无任何异步效果,必须配合await

  • await:等待异步任务;仅能写在async方法内部;后面只能绑定 Task / 异步方法

2.2 核心底层特性(背诵)

  1. await等待任务执行期间,不会阻塞UI主线程

  2. 让出主线程权限,主线程可以继续执行其他业务

  3. 异步任务执行完毕,自动切回原线程,继续执行await后续代码

  4. 最大优势:同步式代码写法,实现异步编程效果,代码工整,规避ContinueWith多层回调混乱

2.3 异步方法返回值规范

  • void:事件点击方法专用(按钮点击)

  • Task:无返回值异步方法

  • Task<T>:带返回值异步方法,T为返回数据类型

三、基础同步+异步方法拆分

3.1 普通同步方法

SayHello1、SayHello2:普通同步方法,代码自上而下串行执行,执行完毕才执行下一行

public void SayHello1() { Console.WriteLine("hello world1"); } public void SayHello2() { Console.WriteLine("hello world2"); }

3.2 基础异步方法 SayHello3

逐行拆解执行流程,修正原代码模糊注释

//异步方法:async修饰,不能单独生效 public async void SayHello3() { //1.同步代码:立即执行 Console.WriteLine("dddddddddddddddddddddddddd"); //2.创建并启动原生Task任务 Task t = new Task(() => { Console.WriteLine("hello world3"); }); t.Start(); //3.异步等待:让出主线程,不阻塞UI //后台等待t执行完毕,主线程释放,可以执行其他操作 await t; //4.第一个任务完成,执行第二个异步任务 await Task.Run(() => { Console.WriteLine("另一个任务"); }); }

3.3 窗体构造方法执行顺序

public Form1() { InitializeComponent(); SayHello1();//同步执行 SayHello2();//同步执行 SayHello3();//调用异步方法,遇到await立刻让出主线程,不阻塞构造函数 Console.WriteLine("sssssssssssss");//该行无需等待异步完成,直接执行 }

输出顺序:hello1→hello2→dddd→sssss→hello3→另一个任务

四、四大按钮案例逐行解析

4.1 button1:异步调用基础测试

private void button1_Click(object sender, EventArgs e) { SayHello3();//调用异步方法 //无需等待异步执行完毕,直接执行后续代码 Console.WriteLine("后续执行的代码"); }

执行特点:异步代码后台运行,主线程直接放行,互不阻塞

4.2 button2:获取带返回值异步结果

核心:await自动解析泛型Task结果,替代.Result,不会阻塞线程

private async void button2_Click(object sender, EventArgs e) { Task<int> t1 = GetResult();//调用异步方法 await t1;//等待任务执行,不阻塞UI Console.WriteLine(t1.Result+"ssssssssssssssss"); } /// <summary> /// 有返回值异步业务方法 /// 返回值必须声明 Task<基础类型> /// </summary> public async Task<int> GetResult() { //启动耗时异步任务 Task<int> t = Task.Run(() => { return 100; }); //✅await自动提取任务返回值,赋值变量,最优写法 int num = await t; //两种取值等价:await取值 / Result取值 Console.WriteLine(num+"sss"); Console.WriteLine(t.Result); return t.Result; }

4.3 button3:最简取值案例

简化冗余代码,直观展示await取值原理

private async void button3_Click(object sender, EventArgs e) { //模拟耗时异步任务 Task<int> t = Task.Run(() => { Thread.Sleep(100); return 100; }); //await:阻塞异步流程,不阻塞UI主线程 int num = await t; //两种方式获取返回值,结果完全一致 Console.WriteLine(num); Console.WriteLine(t.Result); }

4.4 button4:异步任务串行顺序执行

对比旧写法ContinueWith链式回调;新版await串行执行,代码极简、可读性极高

private async void button4_Click(object sender, EventArgs e) { #region 老旧写法:ContinueWith链式回调,多层嵌套、代码混乱(回调地狱) //Task<string> t1 = new Task<string>((s) => //{ // Console.WriteLine("任务1"); // return "t1"+s; //},"aaaa"); //t1.Start(); //Task t2 = t1.ContinueWith(preTask => //{ // Console.WriteLine("任务2"); // Console.WriteLine(preTask.Result); //}); //Task t3 = t2.ContinueWith(preTask => //{ // Console.WriteLine("任务3"); //}); #endregion //✅新式await串行写法:自上而下顺序执行,逻辑清晰 Task<string> t1 = new Task<string>((s) => { Console.WriteLine("任务1"); return "t1" + s; }, "aaaa"); t1.Start(); string s1 = await t1;//等待任务1执行完毕 Task t2 = Task.Run(() => { Console.WriteLine("任务2"); }); await t2;//等待任务2执行完毕 Task t3 = Task.Run(() => { Console.WriteLine("任务3"); }); await t3;//等待任务3执行完毕 }

五、异步五层演进(期末简答题必考)

  1. Thread原生线程:粒度最低,手动创建销毁,开销大,无法管控返回值,极易内存泄漏

  2. ThreadPool线程池:复用线程、优化性能,无法管控任务状态、获取返回值

  3. Task异步任务:基于线程池封装,自带状态、返回值,需要ContinueWith回调,代码嵌套混乱

  4. async+await语法糖:同步代码结构,实现异步逻辑,解决回调地狱,不阻塞UI

  5. 最终演进方向:Thread → ThreadPool → Task → Async/Await

六、易错考点汇总

  1. 单独添加async,没有await,没有任何异步效果,依旧同步执行

  2. await只能写在async修饰的方法内部,普通方法禁止使用await

  3. await后方只能放置 Task、Task<T>、异步方法,不能写普通变量

  4. Result取值阻塞主线程;await取值不阻塞UI,优先使用await

  5. 按钮点击事件异步返回值只能写void,业务异步方法禁止写void,必须返回Task

七、整理完整版可运行代码

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace _3async和await { public partial class Form1 : Form { public Form1() { InitializeComponent(); SayHello1(); SayHello2(); SayHello3(); Console.WriteLine("sssssssssssss"); } public void SayHello1() { Console.WriteLine("hello world1"); } public void SayHello2() { Console.WriteLine("hello world2"); } //可以在方法前添加async关键字 使其变成异步的方法, //如果仅仅添加async 没有异步的效果 配合await进行使用 //await是等待,后面只能跟Task或者异步方法,等待异步任务执行时候,再会执行到主线程里面, //async awai最大特点既有异步的特性不会阻塞,使用同步代码编写方式去实现异步编程 结构更清晰 public async void SayHello3() { Console.WriteLine("dddddddddddddddddddddddddd"); Task t = new Task(() => { Console.WriteLine("hello world3"); }); t.Start(); await t;// 等待异步任务执行,在等待的过程中不会阻塞主线程,回到主线程去执行 await Task.Run(() => { Console.WriteLine("另一个任务"); }); } private void button1_Click(object sender, EventArgs e) { SayHello3(); Console.WriteLine("后续执行的代码"); } private async void button2_Click(object sender, EventArgs e) { Task<int> t1 = GetResult(); await t1; Console.WriteLine(t1.Result+"ssssssssssssssss"); } //返回值是 Task<int>类型 public async Task<int> GetResult() { // 有返回值的异步任务 Task<int> t = Task.Run(() => { return 100; }); int num = await t; //等待异步任务执行,把结果取出来 Console.WriteLine(num+"sss"); Console.WriteLine(t.Result); return t.Result; } private async void button3_Click(object sender, EventArgs e) { // 有返回值的异步任务 Task<int> t = Task.Run(() => { Thread.Sleep(100); return 100; }); int num = await t; //等待异步任务执行,把结果取出来 Console.WriteLine(num); Console.WriteLine(t.Result); } private async void button4_Click(object sender, EventArgs e) { /* //异步任务按照顺序执行 Task<string> t1 = new Task<string>((s) => { Console.WriteLine("任务1"); return "t1"+s; },"aaaa"); t1.Start(); //preTask 上一个任务 t1 Task t2 = t1.ContinueWith(preTask => { Console.WriteLine("任务2"); Console.WriteLine(preTask.Result); }); // t3在t2执行之后执行 Task t3 = t2.ContinueWith(preTask => { Console.WriteLine("任务3"); }); */ Task<string> t1 = new Task<string>((s) => { Console.WriteLine("任务1"); return "t1" + s; }, "aaaa"); t1.Start(); string s1 = await t1; Task t2 = Task.Run(() => { Console.WriteLine("任务2"); }); await t2; Task t3 = Task.Run(() => { Console.WriteLine("任务3"); }); await t3; } } } /* * thread * * threadPool * * Task 和 Task<T> * * Async和Await * * * */

Async+Task实现斐波那契数列渲染案例

笔记承接:承接async/await语法、Task进度条案例,综合大题实操案例

整合知识点:异步语法、取消令牌、递归算法、DataGridView表格渲染、进度条联动、暂停/继续/重置

窗体控件:Button1启动、Button2暂停、Button3继续、Button4停止、ProgressBar进度条、DataGridView表格

二、全局变量逐句释义

四个全局变量管控任务、状态、计数,全部为联动启停核心

namespace _4例子 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } //全局异步任务:管控斐波那契计算任务 Task task; //暂停标记:false运行、true暂停 bool pause = false; //取消令牌:安全管控task任务 CancellationTokenSource cts; //强制停止标记:清空表格、归零进度 bool stop = false; //全局计数器:控制计算1~50位斐波那契数列 int i = 0;

三、核心入口:启动按钮 button1

3.1 方法特征

添加async修饰,内部可以使用await;外层while循环控制整体流程,实现有序串行计算

private async void button1_Click(object sender, EventArgs e) { //启动初始化:清空暂停、停止状态 pause = false; stop = false; //循环判定三大条件 //1.不暂停 2.不强制停止 3.计算位数小于等于50 while (!pause && !stop && i <= 50) { //await:等待每一位斐波那契计算完成,不阻塞UI //拆分计算+UI刷新异步任务 await Calc(i); //计算完成,位数自增 i++; } }

3.2 执行逻辑

  1. 循环每遍历一次,计算1位斐波那契数列

  2. await Calc(i):等待单次计算+页面渲染完毕,再执行下一次循环

  3. 全程不卡死窗体,同步写法实现异步效果

四、业务封装方法:Calc 计算+UI渲染

功能:创建Task异步任务、绑定取消令牌、执行斐波那契计算、刷新进度条+表格

/// <summary> /// 异步封装方法:计算斐波那契 + 刷新UI /// 返回值:Task 支持上层await等待 /// </summary> public Task Calc(int data) { //每一次计算,重新生成取消通行证,禁止复用旧令牌 cts = new CancellationTokenSource(); //实例化异步任务:第二个参数data→向任务传参 task = new Task(s => { //取出当前计算的位数 long num = Convert.ToInt64(s); //调用递归算法,计算斐波那契结果 long result = FiBo(num); try { //跨线程刷新UI:子线程禁止直接操作控件,必须Invoke Invoke(new Action(() => { //进度条联动:位数×2放大进度,填满0~100进度条 progressBar1.Value = (int)num * 2; //表格插入数据:第一列=位数、第二列=计算结果,插入首行 dataGridView1.Rows.Insert(0, new object[] { num, result }); //触发强制停止:清空所有页面状态、复位计数 if (stop) { i = 0; progressBar1.Value = 0; dataGridView1.Rows.Clear(); } })); } catch (Exception) { //捕获UI销毁、线程冲突异常,防止程序闪退 Console.WriteLine("UI渲染异常"); } }, data, cts.Token);//传入参数+绑定取消令牌 task.Start();//启动异步任务 return task;//返回任务,供上层await接收 }

五、递归算法:斐波那契 FiBo

5.1 算法规则

斐波那契数列规则:第1位、第2位数值 = 1;后续每一位 = 前两位之和

/// <summary> /// 递归实现斐波那契数列 /// </summary> public long FiBo(long n) { //递归终止条件:第一位、第二位直接返回1 if (n < 3) { return 1; } //递归公式:前一项+前二项 return FiBo(n - 1) + FiBo(n - 2); }

5.2 课堂易错点

  • n>50递归计算量爆炸,运算极慢,CPU占用飙升

  • 使用long返回值:int范围太小,高位斐波那契数值溢出报错

六、三大功能按钮解析

6.1 Button2 暂停

private void button2_Click(object sender, EventArgs e) { pause = true;//开启暂停标记,阻塞外层while循环 }

6.2 Button3 继续(修复原生逻辑隐患)

原生代码逻辑:直接调用button1_Click,不会重置循环,但是保留原有进度继续执行;缺陷:极端情况产生冗余任务

private void button3_Click(object sender, EventArgs e) { pause = false;//关闭暂停标记,放行循环 //重启触发异步循环,接续原有进度执行 button1_Click(null, null); }

6.3 Button4 强制停止复位

private void button4_Click(object sender, EventArgs e) { stop = true;//开启全局停止标记 i = 0;//计数器归零 progressBar1.Value = 0;//清空进度条 dataGridView1.Rows.Clear();//清空表格数据 }

七、原生代码Bug汇总(考试改错考点)

  1. 递归性能漏洞:第40位之后递归计算巨慢,界面卡顿,递归深度过高

  2. 取消令牌闲置:声明cts但是未调用Cancel,停止任务无法销毁后台线程,轻微内存泄漏

  3. 继续按钮重启任务,存在极小概率任务叠加

  4. 未做窗体关闭资源释放,后台递归常驻进程

八、整体执行流程(背诵版)

  1. 点击启动:初始化状态,进入while循环

  2. 循环调用Calc,创建异步Task,绑定取消令牌

  3. 后台递归计算斐波那契数列,Invoke刷新进度条+表格

  4. await等待单次任务结束,计数器自增,执行下一轮计算

  5. 点击暂停:pause=true阻断循环,暂停运算

  6. 点击继续:关闭暂停标记,接续进度运算

  7. 点击停止:全局复位,清空页面、归零计数

九、核心综合考点

  • 异步嵌套:async外层接收等待,Task内层执行业务

  • UI跨线程更新:所有控件赋值,必须包裹Invoke

  • 状态分流:pause控制暂停、stop强制全局复位,双层状态管控

  • 算法+异步结合:递归耗时业务必须放入子线程,防止UI卡死

十.完整代码

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace _4例子 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } Task task; bool pause = false; CancellationTokenSource cts; bool stop = false; int i =0; private async void button1_Click(object sender, EventArgs e) { pause = false; stop = false; while (!pause&&!stop&&i<=50) { //计算斐波那契 和更新进度一个异步函数 await Calc(i); i++; } } //data = i = s public Task Calc(int data) { cts = new CancellationTokenSource(); task = new Task(s => { //先取出计算第几位的斐波那契数 long num = Convert.ToInt64(s); long result = FiBo(num); try { Invoke(new Action(() => { progressBar1.Value = (int)num * 2; dataGridView1.Rows.Insert(0, new object[] { num, result });//添加行 num 第一列值,result 第二列值 if (stop) { i = 0; progressBar1.Value = 0; dataGridView1.Rows.Clear(); } })); } catch (Exception) { Console.WriteLine(); } //更新表格更新进度 },data,cts.Token); task.Start(); return task; } //封装斐波那契函数的功能 public long FiBo(long n) { if (n<3) { return 1; } return FiBo(n - 1) + FiBo(n - 2); } private void button2_Click(object sender, EventArgs e) { pause = true; } private void button3_Click(object sender, EventArgs e) { pause = false; button1_Click(null, null); } private void button4_Click(object sender, EventArgs e) { stop = true; //cts.Cancel(); i= 0; progressBar1.Value = 0; dataGridView1.Rows.Clear(); } } }
http://www.jsqmd.com/news/1191714/

相关文章:

  • 05-04-YooAsset源码-Unity资源定位系统
  • UE5 Niagara系统实战:从零打造动态烟雾特效的完整指南
  • 探秘ARM Cortex-M系列架构——从M0到M7的演进与选型
  • 国科GK7205V300替代海思HI3516EV300:从硬件兼容到软件迁移的完整指南
  • DS90UB662-Q1多路视频流合并:CSI-2转发模式详解与工程实践
  • Cluade 必备技能 Code-Simplifier
  • 【Java踩坑笔记】44_数据库字段用保留字,MyBatis生成的SQL直接报错
  • 【ChatGPT定价策略深度解密】:20年SaaS产品老兵拆解OpenAI 5次调价背后的成本模型、客户分层与ARPU阈值
  • 五次方程的OSOS结构识别与三次枢纽方程剥离方法
  • 从标准到应用:AVS3与H.265/HEVC视频编解码技术深度对比
  • 【Redis八股|第10篇】Spring Boot 项目整合 Redis 实战
  • C++跨平台剪贴板操作:纯头文件实现与多系统API详解
  • 从《论新阶段》到“活的灵魂”:毛泽东思想核心要义与当代技术创新的思想启迪
  • C++ WinInet实现高效HTTP文件下载:断点续传与多线程优化
  • 2026年7月最新嘉兴美度官方售后客服电话及服务网点地址查询 - 亨得利钟表维修中心
  • WorkBuddy 连接百度网盘:能查能上传,但别指望 AI 帮你下载
  • AWR1843BOOST毫米波雷达在自动泊车系统中的优势与实现
  • 影刀RPA 影刀中的SQL查询大全:从简单查询到多表联查
  • Midscene.js框架(5):测试报告系统与调试技巧
  • 第二章 温度、热量与能量(四)---焓
  • 为什么你的Claude脚本总要人工重写?深度拆解Prompt熵值与代码可执行性之间的3个数学阈值(基于217个真实case统计)
  • 从GESP五级真题解析C++算法思维:有趣的数字和问题
  • 模型监控仪表盘实战:从数据漂移到服务延迟的五维健康监测
  • Cocos Creator 3.8.6 游戏快速上架微信小游戏全流程实战指南
  • 2026工作总结工具哪家好用?5款AI工具实测对比,告别无效熬夜写总结
  • 【电路实战】MOS管缓启动电路的设计要点与参数计算
  • C++20 Ranges实战:filter与transform高效用法与性能优化
  • TI CC2640R2L蓝牙5.1 MCU:超低功耗物联网设计全解析
  • 影刀RPA 影刀流程的单元测试方法:不靠试跑靠验证
  • MSP430F676x1A外设配置与低功耗设计实战指南