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


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

相关推荐
江屿风10 分钟前
C++OJ题经验总结(竞赛)5
开发语言·c++
溪语流沙13 分钟前
【Python项目实战】虚拟环境与依赖管理:venv / pip / requirements.txt实操
开发语言·python·pip
神威难绷泪13 分钟前
Linux应用软件编程:线程分离属性 互斥机制 同步机制 死锁
linux·开发语言·算法·线程
研☆香36 分钟前
数组方法 splice讲解 拓展
开发语言·前端·javascript
淡海水40 分钟前
05-03-栈队列-PriorityQueue-TElement-TPriority-NET6优先队列语义与四叉堆实现
服务器·前端·c#·priorityqueue·clr·telement
峥嵘life1 小时前
Android16 系统 APEX 模块说明
android·大数据·开发语言
2601_962071571 小时前
数据库系统架构与DBMS功能探微:现代信息时代数据管理的关键
java·开发语言·数据库
————A1 小时前
Agent 文件处理中间件
开发语言·javascript·ecmascript
梦想的旅途21 小时前
如何高效调用企业微信通讯录API管理组织架构
java·开发语言·企业微信
whcyhhh1 小时前
头歌实践教学平台:大数据存储2023(十三3)
大数据·开发语言·python