笔记承接:承接Task全套笔记,是多线程最后一节核心语法;Async+Await是C#语法糖,简化异步代码、解决回调地狱,是目前异步编程最优写法
前置学习链路:Thread原生线程→ThreadPool线程池→Task异步任务→async/await语法
窗体控件:button1~button4,分别测试异步执行、返回值、串行任务
二、async与await基础语法(必考)
2.1 两个关键字作用
async:修饰方法,标识该方法为异步方法;单独使用无任何异步效果,必须配合await
await:等待异步任务;仅能写在async方法内部;后面只能绑定 Task / 异步方法
2.2 核心底层特性(背诵)
await等待任务执行期间,不会阻塞UI主线程
让出主线程权限,主线程可以继续执行其他业务
异步任务执行完毕,自动切回原线程,继续执行await后续代码
最大优势:同步式代码写法,实现异步编程效果,代码工整,规避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执行完毕 }五、异步五层演进(期末简答题必考)
Thread原生线程:粒度最低,手动创建销毁,开销大,无法管控返回值,极易内存泄漏
ThreadPool线程池:复用线程、优化性能,无法管控任务状态、获取返回值
Task异步任务:基于线程池封装,自带状态、返回值,需要ContinueWith回调,代码嵌套混乱
async+await语法糖:同步代码结构,实现异步逻辑,解决回调地狱,不阻塞UI
最终演进方向:Thread → ThreadPool → Task → Async/Await
六、易错考点汇总
单独添加async,没有await,没有任何异步效果,依旧同步执行
await只能写在async修饰的方法内部,普通方法禁止使用await
await后方只能放置 Task、Task<T>、异步方法,不能写普通变量
Result取值阻塞主线程;await取值不阻塞UI,优先使用await
按钮点击事件异步返回值只能写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位斐波那契数列
await Calc(i):等待单次计算+页面渲染完毕,再执行下一次循环
全程不卡死窗体,同步写法实现异步效果
四、业务封装方法: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汇总(考试改错考点)
递归性能漏洞:第40位之后递归计算巨慢,界面卡顿,递归深度过高
取消令牌闲置:声明cts但是未调用Cancel,停止任务无法销毁后台线程,轻微内存泄漏
继续按钮重启任务,存在极小概率任务叠加
未做窗体关闭资源释放,后台递归常驻进程
八、整体执行流程(背诵版)
点击启动:初始化状态,进入while循环
循环调用Calc,创建异步Task,绑定取消令牌
后台递归计算斐波那契数列,Invoke刷新进度条+表格
await等待单次任务结束,计数器自增,执行下一轮计算
点击暂停:pause=true阻断循环,暂停运算
点击继续:关闭暂停标记,接续进度运算
点击停止:全局复位,清空页面、归零计数
九、核心综合考点
异步嵌套: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(); } } }