Task异步练习(进度条)

📅 2026/7/8 2:18:59 👁️ 阅读次数 📝 编程学习
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 异步后台进度条【暂停、继续、取消、断点续跑】终极知识总结

一、整体功能(你当前代码实现)

  1. 点击开启:后台线程循环 0~100,每 500ms 刷新进度条、Label、DataGridView
  2. 支持暂停 / 继续(软暂停,不销毁任务、可续跑)
  3. 支持取消(硬终止任务、清空界面)
  4. 防止重复开启多任务
  5. 断点续跑:暂停 / 取消后再次开启从上次数字继续
  6. 自带异常捕获、资源释放、线程安全、UI 不卡顿

二、全局变量 4 个(必考)

csharp

运行

private CancellationTokenSource _cts; // 任务取消器(发停止信号) private bool _isPause = false; // 暂停标记 private bool _isRunning = false; // 任务运行锁(防重复启动) private int _currentNum = 0; // 全局记录当前循环数字(实现断点续跑)

作用一句话

  1. _cts:负责取消任务
  2. _isPause:负责暂停 / 继续
  3. _isRunning:负责防止多开
  4. _currentNum:负责断点续跑

三、窗体构造函数知识点

csharp

运行

progressBar1.Minimum = 0; progressBar1.Maximum = 100; progressBar1.Value = 0; dataGridView1.Columns.Add("colNum", "数字"); dataGridView1.Columns.Add("colVal", "值");
  1. 进度条范围0~100和循环完全对齐
  2. 代码动态创建表格两列,无需手动设计

四、核心重点:为什么你的代码是真正后台线程

关键区别(你之前最大的疑惑)

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→ 强制终止任务,抛取消异常


九、异常处理机制

  1. OperationCanceledException用户手动取消任务 →正常预期操作
  2. 通用 Exception 程序 Bug、计算错误、控件错误 →兜底保护程序不崩溃

十、资源释放(重要)

csharp

运行

finally { _isRunning = false; _cts?.Dispose(); _cts = null; }
  • 无论成功、报错、取消一定执行
  • 释放取消令牌资源,防止内存泄漏

十一、你代码的完美架构总结(背诵版)

  1. Task.Run开启后台线程 → UI 永不卡死
  2. 全局变量记录状态→ 实现暂停、继续、续跑、防多开
  3. token 令牌→ 安全取消任务
  4. Invoke→ 解决跨线程 UI 报错
  5. async/await→ 优雅异步,不阻塞
  6. try-catch-finally→ 安全稳定、资源不泄漏

十二、最终一句话超级总结

  • Task.Run 负责后台运行
  • while (_isPause) 负责暂停
  • CancellationToken 负责取消
  • 全局变量负责续跑、防多开
  • Invoke 负责跨线程更新 UI
  • async/await 负责不卡界面