You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WinForm通过按钮控制打印线程启动/停止/暂停/恢复

核心问题

你之前尝试的所有方案失效,本质是三个低级错误:

  1. 传入工作线程的variablesStatus是值类型bool,方法接收的是原变量的副本,UI线程修改原变量时,工作线程持有的副本不会同步变化,根本感知不到状态变更。
  2. 所有暂停、取消的控制信号都没有传入工作线程的循环逻辑,foreach执行过程中完全没有状态检查的逻辑,自然不会响应外部控制。
  3. 直接把窗体、文本框、进度条、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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 07:12:27