从零打造 C# 工业视觉检测系统(十一):Task 异步编程与实时性保障
系列标签 :
C#Taskasync/awaitInvokeCancellationToken实时性本文目标:掌握工业软件中的多线程任务管理、UI 线程安全和性能优化
一、前言
工业视觉检测系统对实时性 和稳定性有极高要求。检测循环需要在毫秒级完成,同时 UI 界面必须保持流畅响应。这离不开合理的多线程设计。
本文将深入讲解本项目中的 Task 异步编程实践,以及工业软件的性能保障策略。
二、为什么需要多线程?
假设所有操作都在 UI 线程执行:
UI 线程: [拍照]→[算法推理 50ms]→[更新UI]→[存数据库 20ms]→[下一轮]
问题:
- 界面完全卡死,无法点击停止按钮
- 算法耗时波动时,帧率不稳定
- 数据库连接超时会阻塞整个流程
正确的设计:
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 性能优化建议
- 减少 GC 压力:复用 Bitmap 和缓冲区,避免频繁 new 对象
- 控制刷新频率:UI 刷新不需要每帧都执行,50-100ms 一次足够
- 数据库批量插入:单条 INSERT 改为批量插入,减少网络往返
- 算法耗时超标报警:当处理时间超过阈值时记录日志或报警
六、常见问题
Q1:Task 异常未捕获?
使用 try-catch 包裹循环体,或在 Task.ContinueWith 中处理异常。
Q2:Invoke 抛出"在创建句柄之前无法调用 Invoke"?
确保窗体已加载完成再启动后台任务,或在 Form_Load 中初始化。
Q3:内存不断增长?
检查 Bitmap 是否及时 Dispose,事件委托是否正确取消订阅。
七、总结
本文深入讲解了工业软件的多线程设计:
- Task.Run 创建后台检测循环
- CancellationToken 实现优雅停止
- BeginInvoke/Invoke 安全更新 UI
- 耗时监控 与性能优化策略
下篇预告:《从零打造 C# 工业视觉检测系统(十二):P/Invoke 调用 C++ 算法 DLL 与 ONNX 模型部署》
欢迎点赞、收藏、关注,你的支持是我持续输出的动力!