从零打造 C# 工业视觉检测系统(十一):Task 异步编程与实时性保障

从零打造 C# 工业视觉检测系统(十一):Task 异步编程与实时性保障

系列标签C# Task async/await Invoke CancellationToken 实时性

本文目标:掌握工业软件中的多线程任务管理、UI 线程安全和性能优化


一、前言

工业视觉检测系统对实时性稳定性有极高要求。检测循环需要在毫秒级完成,同时 UI 界面必须保持流畅响应。这离不开合理的多线程设计。

本文将深入讲解本项目中的 Task 异步编程实践,以及工业软件的性能保障策略。


二、为什么需要多线程?

假设所有操作都在 UI 线程执行:

复制代码
UI 线程: [拍照]→[算法推理 50ms]→[更新UI]→[存数据库 20ms]→[下一轮]

问题:

  1. 界面完全卡死,无法点击停止按钮
  2. 算法耗时波动时,帧率不稳定
  3. 数据库连接超时会阻塞整个流程

正确的设计

复制代码
UI 线程: [响应用户操作] [更新统计数字]
            ↑                ↑
后台线程: [拍照→算法→存库] 通过 Invoke 更新

三、Task 与 CancellationToken

3.1 检测循环的 Task 实现

RunningForm 的核心检测循环:

csharp 复制代码
Task sortTask;
CancellationTokenSource sortCancelTokenSource;

private void StartSort()
{
    sortCancelTokenSource = new CancellationTokenSource();
    sortTask = Task.Run(async () =>
    {
        while (!sortCancelTokenSource.Token.IsCancellationRequested)
        {
            DateTime startTime = DateTime.Now;

            int ret = DllMethod.start(CameraData.CameraIndex[0], out outSlug);

            DateTime endTime = DateTime.Now;
            TimeSpan duration = endTime - startTime;
            Console.WriteLine("处理时间: " + duration.TotalMilliseconds + " 毫秒");

            if (ret == 1)
            {
                totalCount++;
                if (outSlug.is_qualified == true)
                    qualifiedCount++;
                else
                    unQualifiedCount++;

                qualifiedYield = Math.Round(((double)qualifiedCount / totalCount) * 100, 1);

                // 跨线程更新 UI
                this.BeginInvoke(new Action(() =>
                {
                    RefreshTextBox();
                }));

                SaveSlug(outSlug);
            }

            await Task.Delay(5);  // 减少 CPU 占用
        }
    }, sortCancelTokenSource.Token);
}

private void CloseSort()
{
    sortCancelTokenSource.Cancel();
    sortTask.Wait();
    sortCancelTokenSource?.Dispose();
}

3.2 CancellationToken 优雅停止

csharp 复制代码
// 发出取消信号
sortCancelTokenSource.Cancel();

// 等待任务自然退出(避免强制终止导致资源泄漏)
sortTask.Wait();

// 释放资源
sortCancelTokenSource?.Dispose();

关键点

  • Cancel() 只是设置标志位,不会强制中断线程
  • 循环中必须检查 IsCancellationRequested
  • Wait() 等待任务完成后再继续,确保资源正确释放

四、Invoke vs BeginInvoke

4.1 区别

方法 同步/异步 使用场景
Invoke 同步 需要立即获取 UI 操作结果
BeginInvoke 异步 不需要等待,减少后台线程阻塞

4.2 项目中的应用

使用 BeginInvoke(推荐)

csharp 复制代码
this.BeginInvoke(new Action(() =>
{
    FrontPic.Image = frontBitmap;
    FrontPic.Refresh();
}));

使用 Invoke

csharp 复制代码
this.Invoke(new Action(() =>
{
    FrontPic.Image = null;
    FrontPic.Refresh();
}));

原则

  • 取流显示用 BeginInvoke,避免阻塞取流循环
  • 清理资源用 Invoke,确保顺序执行

五、性能监控

5.1 算法耗时测量

csharp 复制代码
DateTime startTime = DateTime.Now;
int ret = DllMethod.start(CameraData.CameraIndex[0], out outSlug);
DateTime endTime = DateTime.Now;
TimeSpan duration = endTime - startTime;
Console.WriteLine("处理时间: " + duration.TotalMilliseconds + " 毫秒");

5.2 性能优化建议

  1. 减少 GC 压力:复用 Bitmap 和缓冲区,避免频繁 new 对象
  2. 控制刷新频率:UI 刷新不需要每帧都执行,50-100ms 一次足够
  3. 数据库批量插入:单条 INSERT 改为批量插入,减少网络往返
  4. 算法耗时超标报警:当处理时间超过阈值时记录日志或报警

六、常见问题

Q1:Task 异常未捕获?

使用 try-catch 包裹循环体,或在 Task.ContinueWith 中处理异常。

Q2:Invoke 抛出"在创建句柄之前无法调用 Invoke"?

确保窗体已加载完成再启动后台任务,或在 Form_Load 中初始化。

Q3:内存不断增长?

检查 Bitmap 是否及时 Dispose,事件委托是否正确取消订阅。


七、总结

本文深入讲解了工业软件的多线程设计:

  1. Task.Run 创建后台检测循环
  2. CancellationToken 实现优雅停止
  3. BeginInvoke/Invoke 安全更新 UI
  4. 耗时监控 与性能优化策略

下篇预告:《从零打造 C# 工业视觉检测系统(十二):P/Invoke 调用 C++ 算法 DLL 与 ONNX 模型部署》


欢迎点赞、收藏、关注,你的支持是我持续输出的动力!

相关推荐
c#上位机2 小时前
C#上位机项目实战——Task.Run执行带参数的方法
c#·上位机
Bruce_Liuxiaowei4 小时前
Python 实例赋值遮蔽类属性:一个从不报错的静默陷阱
开发语言·python·语法糖
aramae5 小时前
MySQL内置函数(7)
开发语言·笔记·后端·mysql·其他
Lost of 程序猿8 小时前
用 YARP 为 .NET 微服务搭一座“立交桥“:路由转发与生产配置实践
c#·asp.net
码行山野赴时序归途8 小时前
链表家族收官篇:循环链表
c语言·开发语言·数据结构·链表
景熙55239 小时前
单体项目编写思路和落地形式
java·开发语言
计算机毕设定制辅导-无忧学长9 小时前
《基于SpringBoot的未成年人健康知识科普平台的设计与实现》
java·开发语言·vue.js·spring boot·未成年人健康知识科普平台
Ivanqhz9 小时前
Ping-Pong 双缓冲
开发语言·人工智能·python·深度学习·mlir
m0_7345717610 小时前
深入理解C++ RAII
开发语言·c++
泡海椒10 小时前
jquick-pdf 表格实战:动态数据 PDF 报表生成
java·开发语言·pdf