Task异步练习(进度条)
WinForm窗体设计:
代码:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace 进度条 { public partial class Form1 : Form { private CancellationTokenSource _cts;//通行证 private bool _isPause=false;//暂停标记 private bool _isRunning=false;//运动标记 private int _currentNum = 0;//值 public Form1() { InitializeComponent(); progressBar1.Minimum = 0;//设置最小进度条 progressBar1.Maximum = 100;//设置最大进度条 progressBar1.Value = 0;//进度初始值 dataGridView1.Columns.Add("colNum", "数字");//在表格中添加列(列名,显示在表格中列名) dataGridView1.Columns.Add("colVal", "值"); } private async Task RunLoopAsync(CancellationToken token)//从开启按钮传递过来的通行证,用来监听取消信号 { await Task.Run(async () => { for (; _currentNum <= 100; _currentNum++) { token.ThrowIfCancellationRequested(); // 暂停阻塞逻辑 while (_isPause) { await Task.Delay(1000, token); } int val = _currentNum * 10; // 后台子线程必须Invoke切UI线程更新控件 this.Invoke(new Action(() => { progressBar1.Value = _currentNum; label1.Text = $"当前进度:{_currentNum}/100,数字值:{val}"; dataGridView1.Rows.Add(_currentNum, val); })); await Task.Delay(500, token); } // for循环执行完毕,全部数字跑完 this.Invoke(new Action(() => { label1.Text = "全部执行完毕!"; MessageBox.Show("循环数字已到达100,任务自动结束!"); })); _currentNum = 0; this.Invoke(new Action(() => { progressBar1.Value = 0; })); }, token); // Task.Run 闭合 + 传入取消令牌 } private void btnPause_Click(object sender, EventArgs e) { if (!_isRunning) return; _isPause = true; label1.Text += "【已暂停】"; } private void btnContinue_Click(object sender, EventArgs e) { if(!_isRunning||!_isPause) return; _isPause=false; } private void btnCancel_Click(object sender, EventArgs e) { if(!_isRunning) return; _cts?.Cancel(); label1.Text = null; dataGridView1.Rows.Clear(); progressBar1.Value = 0; _currentNum = 0; } private async void btnStart_Click(object sender, EventArgs e) { if (_isRunning) { MessageBox.Show("任务正在运行,请勿重复开启!"); return; } _isRunning = true; _isPause = false; _cts = new CancellationTokenSource(); CancellationToken token = _cts.Token; try { await RunLoopAsync(token); } catch (OperationCanceledException) { label1.Text = "任务已取消"; } catch (Exception ex) { MessageBox.Show($"运行异常:{ex.Message}"); } finally { _isRunning = false; _cts?.Dispose(); _cts = null; } } } }WinForm 异步后台进度条【暂停、继续、取消、断点续跑】终极知识总结
一、整体功能(你当前代码实现)
- 点击开启:后台线程循环 0~100,每 500ms 刷新进度条、Label、DataGridView
- 支持暂停 / 继续(软暂停,不销毁任务、可续跑)
- 支持取消(硬终止任务、清空界面)
- 防止重复开启多任务
- 断点续跑:暂停 / 取消后再次开启从上次数字继续
- 自带异常捕获、资源释放、线程安全、UI 不卡顿
二、全局变量 4 个(必考)
csharp
运行
private CancellationTokenSource _cts; // 任务取消器(发停止信号) private bool _isPause = false; // 暂停标记 private bool _isRunning = false; // 任务运行锁(防重复启动) private int _currentNum = 0; // 全局记录当前循环数字(实现断点续跑)作用一句话
_cts:负责取消任务_isPause:负责暂停 / 继续_isRunning:负责防止多开_currentNum:负责断点续跑
三、窗体构造函数知识点
csharp
运行
progressBar1.Minimum = 0; progressBar1.Maximum = 100; progressBar1.Value = 0; dataGridView1.Columns.Add("colNum", "数字"); dataGridView1.Columns.Add("colVal", "值");- 进度条范围0~100和循环完全对齐
- 代码动态创建表格两列,无需手动设计
四、核心重点:为什么你的代码是真正后台线程?
关键区别(你之前最大的疑惑)
1.async / await不会开后台线程
2.Task.Run才会开后台线程
你现在正确代码:
csharp
运行
await Task.Run(async () => { // 这里 100% 后台子线程!! });所以:
- 循环、计算、Delay全部跑在线程池后台
- 不卡 UI
- 必须 Invoke 更新控件(否则报错跨线程)
五、核心方法 RunLoopAsync 逐知识点
1. 方法标准规范
csharp
运行
private async Task RunLoopAsync(CancellationToken token)- 自定义异步方法必须返回
Task(不能 async void) token:接收取消信号,用来随时终止循环
2. 断点续跑 for 循环(高频考点)
csharp
运行
for (; _currentNum <= 100; _currentNum++)- 没有初始化语句
- 循环变量用全局字段
- 暂停、取消后数字不会丢 →实现断点续跑
3. 任务取消检测
csharp
运行
token.ThrowIfCancellationRequested();- 检测是否点击了取消
- 点击取消直接抛异常 → 终止后台任务
4. 软暂停逻辑(重点)
csharp
运行
while (_isPause) { await Task.Delay(1000, token); }- 不销毁任务、不退出循环
- 原地无限等待 →暂停
- 改
_isPause=false→立刻继续
5. Invoke 跨线程更新 UI(必考)
csharp
运行
this.Invoke(new Action(() => { progressBar1.Value = _currentNum; label1.Text = $"当前进度:{_currentNum}/100,数字值:{val}"; dataGridView1.Rows.Add(_currentNum, val); }));为什么必须 Invoke?
- 循环代码在后台子线程
- WinForm 控件只能由 UI 主线程访问
- 子线程改控件 = 报错
- Invoke:把子线程代码切回 UI 主线程执行
6. 异步延时
csharp
运行
await Task.Delay(500, token);- 不阻塞线程、不卡界面
- 带 token:取消时立刻中断等待
六、任务跑完收尾逻辑
csharp
运行
this.Invoke(new Action(() => { label1.Text = "全部执行完毕!"; MessageBox.Show("循环数字已到达100,任务自动结束!"); })); _currentNum = 0;- 跑完归零,下次从头开始
七、四个按钮逻辑总结(超级清晰)
1. 开启按钮(核心入口)
- 判断
_isRunning防止重复开启 - 创建
_cts取消源 - 传递 token 给后台任务
try-catch捕获取消异常、程序异常- finally 必执行:释放资源、解锁任务状态
2. 暂停按钮
csharp
运行
_isPause = true;后台while进入死循环等待 → 暂停
3. 继续按钮
csharp
运行
_isPause = false;退出 while 循环 → 继续跑
4. 取消按钮
csharp
运行
_cts.Cancel();- 给后台发终止信号
- 清空表格、进度条、数字
八、三大核心机制区别(考试必考)
1. 后台线程机制
Task.Run→ 真正后台运行,UI 不卡、必须 Invoke
2. 暂停机制(软暂停)
布尔变量控制,任务不销毁、线程不退出、可续跑
3. 取消机制(硬终止)
CancellationTokenSource→ 强制终止任务,抛取消异常
九、异常处理机制
OperationCanceledException用户手动取消任务 →正常预期操作- 通用 Exception 程序 Bug、计算错误、控件错误 →兜底保护程序不崩溃
十、资源释放(重要)
csharp
运行
finally { _isRunning = false; _cts?.Dispose(); _cts = null; }- 无论成功、报错、取消一定执行
- 释放取消令牌资源,防止内存泄漏
十一、你代码的完美架构总结(背诵版)
- Task.Run开启后台线程 → UI 永不卡死
- 全局变量记录状态→ 实现暂停、继续、续跑、防多开
- token 令牌→ 安全取消任务
- Invoke→ 解决跨线程 UI 报错
- async/await→ 优雅异步,不阻塞
- try-catch-finally→ 安全稳定、资源不泄漏
十二、最终一句话超级总结
- Task.Run 负责后台运行
- while (_isPause) 负责暂停
- CancellationToken 负责取消
- 全局变量负责续跑、防多开
- Invoke 负责跨线程更新 UI
- async/await 负责不卡界面