从零打造 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 模型部署》


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

相关推荐
czhc11400756631 小时前
2026-08-09 学习笔记:背钻检测核心概念
c#
BUG研究员_11 小时前
Runnable与LCEL
开发语言·人工智能·python
牛艺翔12 小时前
C++基础
开发语言·c++
键盘会跳舞13 小时前
C++ :容器适配器stack源码级拆解
开发语言·c++··stack·先进后出
美味蛋炒饭.13 小时前
Git 版本控制(下)
开发语言·git·学习·总结·后端开发
yanaiding13 小时前
C# WPF 独立开发看图工具 PixSight 经验介绍(二)—— 水印模块开发实录
图像处理·c#·wpf·个人开发
我星期八休息13 小时前
网络编程—网络层
开发语言·前端·网络·人工智能·智能路由器
问商十三载13 小时前
RAG 检索效果差怎么排查?2026 五层诊断法完整指南
开发语言·人工智能·windows·python·算法
不负岁月无痕13 小时前
简单理解操作系统结构
java·linux·c语言·开发语言·c++·面试