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


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

相关推荐
懂点Tech5 小时前
ctfshow misc28:GIF 高度修改解题与脚本实现
开发语言·windows·python
云和数据.ChenGuang5 小时前
Python 重写(Override)深度解析:从 MRO 到 super() 的完整机制
java·开发语言·人工智能·python·langchain·1024程序员节
美狐美颜SDK开放平台6 小时前
视频美颜SDK试用DEMO阶段应该测试什么?直播APP接入实操指南
开发语言·人工智能·深度学习·音视频·美颜sdk·直播美颜sdk·第三方美颜sdk
dtq04246 小时前
C++小案例 - 日志实战
开发语言·c++
星夜离尘567 小时前
MATLAB ADC 码流触顶诊断:命中数、缺口分段与削顶不可辨识性
开发语言·matlab
码匠许师傅7 小时前
【C++三方组件】Qt 概览:桌面 C++ 的事实标准与许可证三轨
开发语言·c++·qt
码匠许师傅8 小时前
【C++三方组件】POCO:跨平台系统编程全家桶
开发语言·c++
bkspiderx9 小时前
基于Socket的广播通信及C语言实现详解
c语言·开发语言·socket·广播·socket的广播通信
小王C语言9 小时前
Qt常用控件:输入类控件,单行/多行输入框、下拉框、(日期)微调框、旋钮、滑动条
开发语言·qt
ESDWAN9 小时前
SD-WAN 主备切换为什么仍然会断网?从检测机制到会话恢复分析
开发语言·php