C# WinForm通过按钮控制打印线程启动/停止/暂停/恢复
核心问题
你之前尝试的所有方案失效,本质是三个低级错误:
- 传入工作线程的
variablesStatus是值类型bool,方法接收的是原变量的副本,UI线程修改原变量时,工作线程持有的副本不会同步变化,根本感知不到状态变更。 - 所有暂停、取消的控制信号都没有传入工作线程的循环逻辑,
foreach执行过程中完全没有状态检查的逻辑,自然不会响应外部控制。 - 直接把窗体、文本框、进度条、DataGridView这些UI控件传给工作线程访问,本身就违反WinForm的线程安全规则,轻则状态错乱重则直接抛跨线程访问异常。
可直接运行的实现代码
窗体端代码
public partial class Program_Form : Form { // 线程控制对象必须定义为窗体级字段,所有按钮事件才能访问到同一个实例 private CancellationTokenSource _cts; private ManualResetEventSlim _pauseEvent; private bool _isRunning = false; private void Program_Form_Load(object sender, EventArgs e) { // 初始状态下暂停、停止按钮不可用 BTN6PStop.Enabled = false; BTN6PPauza.Enabled = false; } // 开始打印按钮 private async void DGW6BtnPrint_Click(object sender, EventArgs e) { if (_isRunning) return; // 防止重复启动任务 // 初始化控制信号 _cts = new CancellationTokenSource(); _pauseEvent = new ManualResetEventSlim(true); // 初始值为true,即启动后不暂停 _isRunning = true; // 切换按钮状态 DGW6BtnPrint.Enabled = false; BTN6PStop.Enabled = true; BTN6PPauza.Enabled = true; BTN6PPauza.Text = "暂停"; try { // 提前在UI线程把所有待打印文件路径提取出来,禁止工作线程直接访问DataGridView var fileList = dataGridView6.Rows .Cast<DataGridViewRow>() .Select(row => row.Cells[4].Value?.ToString()) .Where(path => !string.IsNullOrWhiteSpace(path)) .ToList(); int totalFiles = fileList.Count; // 启动工作线程执行打印 await Task.Run(() => { var printer = new Work.Printer_(); printer.Print_File( fileList, _cts.Token, _pauseEvent, // 进度回调:工作线程触发,切回UI线程更新控件 current => { Invoke(new Action(() => { progressBar1.Value = (int)((current + 1) * 100.0 / totalFiles); StatusText.Text = $"正在打印:{current + 1}/{totalFiles}"; })); }); }, _cts.Token); StatusText.Text = "打印任务完成"; } catch (OperationCanceledException) { StatusText.Text = "打印任务已手动停止"; } finally { // 任务结束后重置所有状态,释放资源 _isRunning = false; _cts.Dispose(); _pauseEvent.Dispose(); DGW6BtnPrint.Enabled = true; BTN6PStop.Enabled = false; BTN6PPauza.Enabled = false; BTN6PPauza.Text = "暂停"; progressBar1.Value = 0; } } // 暂停/继续切换按钮 private void BTN6PPauza_Click(object sender, EventArgs e) { if (!_isRunning) return; if (_pauseEvent.IsSet) { // 当前处于运行状态,触发暂停 _pauseEvent.Reset(); BTN6PPauza.Text = "继续"; StatusText.Text = "打印已暂停"; } else { // 当前处于暂停状态,恢复运行 _pauseEvent.Set(); BTN6PPauza.Text = "暂停"; } } // 停止按钮 private void BTN6PStop_Click(object sender, EventArgs e) { if (!_isRunning || _cts == null) return; _cts.Cancel(); // 如果当前处于暂停状态,先放行线程才能响应取消信号 if (!_pauseEvent.IsSet) _pauseEvent.Set(); } }
业务类代码
namespace OfficeTools.Work { class Printer_ { /// <summary> /// 执行文件打印 /// </summary> /// <param name="filePaths">待打印的文件路径列表</param> /// <param name="cancelToken">取消控制令牌</param> /// <param name="pauseWaitHandle">暂停控制信号</param> /// <param name="progressReport">进度上报回调,参数为当前已完成的文件索引</param> public void Print_File( List<string> filePaths, CancellationToken cancelToken, ManualResetEventSlim pauseWaitHandle, Action<int> progressReport) { for (int i = 0; i < filePaths.Count; i++) { // 每轮循环第一步先检查控制信号,顺序不能乱 // 1. 检查是否收到停止指令,收到就直接抛出取消异常退出 cancelToken.ThrowIfCancellationRequested(); // 2. 检查是否暂停,暂停状态下会在这里阻塞,直到收到恢复/停止指令 pauseWaitHandle.Wait(cancelToken); // 执行实际打印逻辑 string currentFile = filePaths[i]; PrintFiles.Print_Word(currentFile); // 上报进度 progressReport?.Invoke(i); } } } }
实现说明
- 停止功能用.NET标准的
CancellationTokenSource实现协作式取消,不要用废弃的Thread.Abort(),后者会强制杀线程导致文件句柄泄漏、打印任务异常中断留后台进程。 - 暂停/恢复功能用
ManualResetEventSlim实现,Reset()方法会阻塞Wait()调用实现暂停,Set()方法放行实现恢复,支持传入取消令牌,停止时哪怕在暂停状态也能立刻响应。 - 所有UI控件操作全部放在UI线程执行,工作线程只处理纯数据计算和打印逻辑,彻底避免跨线程访问异常。
- 不要传值类型的状态变量给工作线程当控制信号,引用类型的线程同步对象才能保证UI线程和工作线程拿到的是同一个实例,状态变更能实时同步。
内容的提问来源于stack exchange,提问作者Stoica Vasile
相关产品推荐
相关产品推荐

