C#多线程之图像处理、数据库读写及PLC指令内部协调问题,大家有何高招

ndxlsfjnl 2020-06-20 05:05:12
在实际应用中,常常会遇到高速产品线图像处理项目,如要求每秒钟处理至少2~3个图像,并将产品数据存入数据库,不合格则剔除产品。
为了提高运行效率,常使用多线程的方式处理图像信息,及后续的数据库读写、PLC指令发送等等。但是,在确保程序基本逻辑功能无误的情况下,有时PLC串口指令未正常发送,导致部分产品漏检,合格产品或多或少。
原因分析:①多线程可能未完整执行;②数据库读写耗时太长;③串口发送耗时(指令无误)
贴关键代码:
首先开启多线程(函数采用事件触发,不知此线程是否有问题):

显示部分:

数据库读写及PLC指令(有时未正常执行):



如何更好的协调多线程中图像处理、数据库读写及PLC指令,使之能满足高速生产的要求呢?大家有何良策?谢谢指导!
...全文
8538 23 打赏 收藏 转发到动态 举报
写回复
用AI写文章
23 条回复
切换为时间正序
请发表友善的回复…
发表回复
ndxlsfjnl 2020-07-22
  • 打赏
  • 举报
回复
引用 11 楼 wanghui0380 的回复:
UI部分未实现,逻辑能跑通。UI是小事情,有context对象,MVVM绑定即可。


感谢您的建议,问题初步解决,系统基本功能运行正常!

仔细研究您给的提议,研究了缓存、Task、MVVM等技术。经过考虑,使用静态变量存储的方式替代缓存机制(感觉此方法不合适),同时加入生产批号,提高检索速度;使用Task异步线程替代原有的Thread方式,效果略有改善;应用MVVM + INotifyPropertyChanged事件触发方式,优化数据显示部分。

总体上,实现了既定的功能,在此作个反馈,感谢您的建议!
ndxlsfjnl 2020-07-22
  • 打赏
  • 举报
回复
感谢您的建议,问题初步解决,系统基本功能运行正常!

仔细研究您给的提议,研究了缓存、Task、MVVM等技术。经过考虑,使用静态变量存储的方式替代缓存机制(感觉此方法不合适),同时加入生产批号,提高检索速度;使用Task异步线程替代原有的Thread方式,效果略有改善;应用MVVM + INotifyPropertyChanged事件触发方式,优化数据显示部分。

总体上,实现了既定的功能,在此作个反馈,感谢您的建议!
  • 打赏
  • 举报
回复
可以改完PLC寄存器值再用串口读一次看看有没有改成功,这样不容易报错
ndxlsfjnl 2020-06-28
  • 打赏
  • 举报
回复
引用 19 楼 华芸智森 的回复:
我没有仔细看楼主的贴。但一旦和图象有关的。我第一反应就是 : Bitmap.LockBits , 将图像数据COPY出来放到一个内存数组中,然后对这个数组(看图像的格式,都是 R,G,B,A 中的几个或所有值 )做任意的处理,完成后再将这个数组用 Marshal.Copy 回到图象中。不要说一秒处理三五个。一秒处理上百个或更多都没有问题。


感谢回复,您提出的图像处理建议很好,不过本程序可能更偏向于后期的数据分析、读写及反馈等部分。
华芸智森 2020-06-28
  • 打赏
  • 举报
回复
我没有仔细看楼主的贴。但一旦和图象有关的。我第一反应就是 : Bitmap.LockBits , 将图像数据COPY出来放到一个内存数组中,然后对这个数组(看图像的格式,都是 R,G,B,A 中的几个或所有值 )做任意的处理,完成后再将这个数组用 Marshal.Copy 回到图象中。不要说一秒处理三五个。一秒处理上百个或更多都没有问题。
ndxlsfjnl 2020-06-23
  • 打赏
  • 举报
回复
引用 15 楼 我了个擦啊啊啊 的回复:
工控方面处理高速处理数据,不建议使用串口,PC里面用PCIE的IO卡走IO比较快,或者直接自己控制执行机构
数据库的数据可以在后台慢慢写,这个项目的难点应该就是剔除掉物料,理论上流水线式的剔除物料是一个接一个的队列,PLC串口发送失败没有反馈吗?


感谢建议,确实PCIE方式比串口通讯方式好点,另外串口发送无反馈,不是发送失败,应该是未发送或发送进程被下一个线程占用,个人想法。
ndxlsfjnl 2020-06-23
  • 打赏
  • 举报
回复
引用 14 楼 wanghui0380 的回复:
foreach (var context in quque.GetConsumingEnumerable())
{
await context.sendplc();

}

这个有个阻塞是为了保证顺序的,也就是如果A,B,C到达,无论谁先执行完毕还是按照A,B,C的顺序给plc发指令(因不知道你的场景,我见过的系统90%需要保证顺序。)

当然这里有小问题。就是我上面说的plc响应时间。
测试里我写了一个1000ms预检查和4000ms预检查

他执行顺序是
引用
4000 到达
4000预检查
1000到达
1000预检查
1000检查完毕
4000检查完毕
4000 对plc发指令
1000对plc发指令

你可以看到他们是并行执行预检查,但发送plc部分按初始到达发送。
问题是最后两条

引用
4000 对plc发指令
1000对plc发指令


这两条物理上是保证顺序,但执行其实没有间隔,所以相当与快速连续两次给plc发。

因为不知道具体应用场景,这里你是否需要过滤的短时间连续2次的,还是需要使用“漏桶式策略”就得自己看了


好的,非常感谢,会认真考虑您的建议,有效果会及时在这里反馈,供其他人思考学习。
ndxlsfjnl 2020-06-23
  • 打赏
  • 举报
回复
引用 9 楼 datafansbj 的回复:
数据库能不能先用缓存替代,用以排除数据库的影响,因为从描述中可以看出,项目对实时性要求较高,数据库恰恰是拖后腿的存在。


谢谢建议,考虑使用缓存技术测试一下。
  • 打赏
  • 举报
回复
工控方面处理高速处理数据,不建议使用串口,PC里面用PCIE的IO卡走IO比较快,或者直接自己控制执行机构 数据库的数据可以在后台慢慢写,这个项目的难点应该就是剔除掉物料,理论上流水线式的剔除物料是一个接一个的队列,PLC串口发送失败没有反馈吗?
泡泡龙 2020-06-22
  • 打赏
  • 举报
回复
所有的多线程操作都要写到一个队列里面来判断是不是真正的执行了
wanghui0380 2020-06-22
  • 打赏
  • 举报
回复
foreach (var context in quque.GetConsumingEnumerable()) { await context.sendplc(); } 这个有个阻塞是为了保证顺序的,也就是如果A,B,C到达,无论谁先执行完毕还是按照A,B,C的顺序给plc发指令(因不知道你的场景,我见过的系统90%需要保证顺序。) 当然这里有小问题。就是我上面说的plc响应时间。 测试里我写了一个1000ms预检查和4000ms预检查 他执行顺序是
引用
4000 到达 4000预检查 1000到达 1000预检查 1000检查完毕 4000检查完毕 4000 对plc发指令 1000对plc发指令
你可以看到他们是并行执行预检查,但发送plc部分按初始到达发送。 问题是最后两条
引用
4000 对plc发指令 1000对plc发指令
这两条物理上是保证顺序,但执行其实没有间隔,所以相当与快速连续两次给plc发。 因为不知道具体应用场景,这里你是否需要过滤的短时间连续2次的,还是需要使用“漏桶式策略”就得自己看了
kxyzjm62 2020-06-22
  • 打赏
  • 举报
回复
向大家学习学习
wanghui0380 2020-06-22
  • 打赏
  • 举报
回复
上面方案是单机的方案 如果是多机的方案,需要引入etcd这类东西去共享上下文和执行状态。 那就是另外一说的了-----分布式共享任务
wanghui0380 2020-06-22
  • 打赏
  • 举报
回复
UI部分未实现,逻辑能跑通。UI是小事情,有context对象,MVVM绑定即可。
wanghui0380 2020-06-22
  • 打赏
  • 举报
回复
因不知道,具体场景,我只按一般场景写了一个框架 大体思路,前面那部分我叫数据预处理,此部分运行并行处理,采用task直接并行异步处置 发送plc因为涉及时序,此部分需要他按时序部分处理,所以采用阻塞队列 //缓存判定部分不想实现了,先给粗框架,后面部分需要根据场景去完成

public partial class Form3 : Form
    {
        //单机模拟,直接使用内存级缓存,按你的描述来看,我估计是多机多点位,只有这样才可能是
        //有那个判定问题,这种我们可以选择Redis进行多机共享缓存
        ObjectCache cache = new MemoryCache("test");
        public Form3()
        {
            InitializeComponent();
        }

        datamake _datamake = new datamake();
        private void Form3_Load(object sender, EventArgs e)
        {
            _datamake.DataEvent += _datamake_DataEvent;
            Task.Run(async () =>
            {
                foreach (var context in quque.GetConsumingEnumerable())
                {
                    await context.sendplc();
                }
            });

        }

        BlockingCollection<Context> quque = new BlockingCollection<Context>(200);
        private void _datamake_DataEvent(KeyValuePair<DateTime, int> obj)
        {
            var context = new Context();
            context.value = obj.Value;
            context.date = obj.Key;
            context.handler();
            quque.Add(context);
        }

        //先做一个你那个事件产生的模拟器
        public class datamake
        {
            public event Action<KeyValuePair<DateTime, int>> DataEvent;

            public void startMake()
            {
                Random rand = new Random(DateTime.Now.Millisecond);
                Task.Run(async () =>
                {
                    while (true)
                    {
                        //随机产生一个数
                        int value = rand.Next(1, 4000);

                        DataEvent?.BeginInvoke(new KeyValuePair<DateTime, int>(DateTime.Now, value), null, null);
                        await Task.Delay(TimeSpan.FromMilliseconds(300)); //大概每秒3次
                    }
                });
            }
        }


        public class Context
        {
            public DateTime date { get; set; }
            public int value { get; set; }

            /// <summary>
            /// 数据预处理任务
            /// </summary>
            private Task pretask;
            public  Task handler()
            {
              return  pretask = Task.Run(async () =>
                  {
                      await get条码();
                      await 检查条码();

                  });


                

            }

            private Task get条码()
            {
                Task.Yield();

                return Task.CompletedTask;
            }

            private async Task 检查条码()
            {
                Task.Yield();

                await Task.Delay(this.value);
                //  return Task.CompletedTask;
            }


            public async Task sendplc()
            {
                //等待数据预处理完毕
                await pretask;

                Trace.WriteLine(this.value);
                //do send plc;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //测试sendplc执行顺序
            var context = new Context();
            context.value = 4000;
            context.date = DateTime.Now; ;
            context.handler();
            quque.Add(context);
            var context2 = new Context();
            context2.value = 1000;
            context2.date = DateTime.Now; ;
            context2.handler();
            quque.Add(context2);
        }

        private void button2_Click(object sender, EventArgs e)
        {
            _datamake.startMake();
        }
    }
datafansbj 2020-06-22
  • 打赏
  • 举报
回复
数据库能不能先用缓存替代,用以排除数据库的影响,因为从描述中可以看出,项目对实时性要求较高,数据库恰恰是拖后腿的存在。
方正夜明 2020-06-21
  • 打赏
  • 举报
回复
还是要跟踪测试,确保代码切实完整执行了,还是说执行过程中有异常存在
ndxlsfjnl 2020-06-21
  • 打赏
  • 举报
回复
引用 5 楼 方正夜明 的回复:
还是要跟踪测试,确保代码切实完整执行了,还是说执行过程中有异常存在


执行过程无异常错误弹出
ndxlsfjnl 2020-06-20
  • 打赏
  • 举报
回复
首先感谢大家指导,下面依次回复问题:
1. 代码内的判定重复:产品图片代表信息与我数据库中已有数据做二次检索比对(①在原始数据内检索一次 ②在已检测数据内检索一次),没有使用缓存技术,相当于二次数据过滤。

2. 单测plc的性能和准确率:关于PLC串口通信性能也测试过很多次。第一,串口指令保证正确(测试多次);第二,以前测试过串口指令发送,反应速度很快(指示灯迅速亮),没有计算反应时间;第三,在现有程序下(多线程环境)测试PLC指令发送,短时间(20~30个)内通讯正常,长时间(100以上)可能会出问题(PLC不动作或延时动作,表明指令发送不成功或延时发送)。

现在确定存在的问题:①多次数据库读写相对耗时(与不读写有明确时间差距,估计0.2s以上)②短时间内一个个测似,不会出现问题,长时间测试就会出现问题(如上午工作无问题,中午程序不关闭,下午就会出现漏检情况),估计还是与线程时序有关,而且界面数据信息更新较慢(异步处理)。
加载更多回复(3)
代码转载自:https://pan.quark.cn/s/11d565366452 【USB转I2C/IIC适配器上位机应用程序】是一种针对I2C(Inter-Integrated Circuit)通信协议而构建的工具,其通过USB端口将个人计算机转变为能够与I2C设备进行交互的控制器。该适配器使开发人员与工程师能够便捷地对遵循I2C协议的芯片执行读取和写入任务,因此在硬件构建和测试阶段能够显著提升效率,对于核实I2C接口芯片的工作性能具有特别的价值。 I2C协议是由飞利浦(现NXP半导体)于1980年代初创立的一种双线式串行总线,其目的是用于连接微控制器及其他外围设备。该协议仅需两条信号线(SDA - 数据线,SCL - 时钟线)即可完成多设备间的通信,大幅度削减了电路板上的布线需求,从而降低了生产成本。I2C协议提供多种速度等级,涵盖标准模式(100kbps)、快速模式(400kbps)以及高速模式(3.4Mbps),旨在满足不同场景下的应用要求。 Ginkgo USB转I2C适配器是这项技术的具体应用实例,它提供了一个物理接口,用于将USB端口转换为I2C端口。适配器内部装配有一个微控制器单元,该单元负责处理从USB到I2C的信号转换,并且通过USB线路向计算机传输信息,同时接收来自计算机的指令以操控I2C总线。这种设计使用户无需具备深厚的硬件知识,仅需借助上位机软件即可实现对I2C设备的操控。 Ginkgo I2C Adapter Classic是与该适配器相配套的软件,通常包含以下几项核心功能: 1. 设备识别:自动检测并建立与USB转I2C适配器的连接。 2. 总线探查:扫描I2C总线上所连接的设备地址,有助于准确定位设备的位置。 3. 读写指令...
代码转载自:https://pan.quark.cn/s/4c053f9292d9 在Android应用程序的设计与实现过程中,有时需要借助C或C++语言所具备的高性能特点,此时就必须借助Android Studio的NDK(Native Development Kit)工具来对C/C++代码进行编译,进而生成SO(Shared Object)动态链接库文件。本篇文章将详尽阐述在Android Studio环境下如何执行这一流程。 我们首先需要掌握Android Studio项目的基本组织架构。在Android Studio的`app`文件夹内,存在一个名为`src`的子文件夹,该文件夹通常包含了`main`以及其他相关源代码目录。在`main`文件夹中,除了我们常见的`java`文件夹外,还设立了一个`jniLibs`文件夹,此文件夹专门用于存储SO文件。除此之外,还有一个`jni`文件夹,它是用来存放C/C++源代码的地方。 为了实现C/C++代码的编写与编译,我们必须在`build.gradle`文件中配置NDK的支持。打开`app/build.gradle`文件,在`defaultConfig`部分中嵌入以下内容: ```groovy externalNativeBuild { ndkBuild { path src/main/jni/Android.mk } } ``` 这段内容明确指示Android Studio去何处寻找`Android.mk`文件,该文件是一种Makefile的变体,主要用于设定编译规则和编译目标。 接下来,我们需要创建`Android.mk`文件。在这个文件中,我们将明确指定源代码文件、编译选项以及要生成的库名称。一个基础的...
内容概要:本文围绕麦克斯韦旋度方程的差分形式在平面极化磁场中的应用展开深入研究,并通过Matlab代码实现相关数值仿真。研究的核心是将经典的麦克斯韦方程组转化为适用于数值计算的有限差分格式,构建离散化数学模型,进而模拟和分析平面极化条件下磁场的空间分布特征与时间演化规律。文中系统阐述了差分方法的理论基础与数学推导过程,详细说明网格划分、边界条件设定及迭代求解策略,最终利用Matlab编程实现电磁场的可视化仿真,有效验证了差分方法在求解复杂电磁场问题中的准确性与可行性。该研究不仅加深了对电磁波传播机理的理解,也为电磁器件的设计优化与工程仿真提供了可靠的数值分析手段。; 适合人群:具备电磁场与电磁波理论基础,熟悉偏微分方程数值解法,且掌握Matlab编程技能的电气工程、物理学、应用数学等领域的研究生、科研人员及工程技术人员。; 使用场景及目标:①深入掌握麦克斯韦旋度方程的有限差分法基本原理及其在二维平面磁场仿真中的具体实施步骤;②学习如何利用Matlab进行电磁场问题的建模、编程求解与结果可视化;③为后续开展更复杂的电磁兼容、天线设计或光子晶体等领域的数值模拟研究奠定坚实的技术基础。; 阅读建议:建议读者结合经典电磁场理论教材,透彻理解麦克斯韦方程组的物理内涵,然后循序渐进地跟进文中的差分格式推导过程,务必动手复现并调试所提供的Matlab代码,通过调整参数观察仿真结果的变化,从而深刻领悟数值方法的实质与应用技巧。

111,129

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Creator Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧