w600 发表于 2020-8-10 23:19:56

C# 串口定时轮询采集中如何插入能及时响应的控制命令

大家好,我有个软件C#开发的,有一个定时的任务,一条485总线上modbus协议采集一批设备数据。目前工作方式是这样的,定时器超时事件发生后,先关闭定时器,在超时事件中采集一批,然后打开定时器,等待下次超时采集。现有有个需求,有个设备需要控制,用户点击时发送控制命令,用户可以随便点。
现在有个问题,用户点击控制时候,定时器的采集任务可能在运行,就会可能出现多条命令同时发的情况,导致接收有问题。现在把所有控制命令缓存起来,等到定时器超时时候再运行,这样不会有多条命令同时发送的情况,但是定时器的定时周期比较长,导致控制命令延时比较大。如果有控制命令修改定时器超时,又会导致原来定时采集的间隔不准确。
在保证定时采集间隔不变,控制又能及时响应的情况下,有没有什么好的机制,既能让定时器的任务始终按照设定的时间运行,又能让插入的控制命令及时的运行?

zxq6 发表于 2020-8-10 23:23:10

多线程不好吗,定时器用起来好像没那么安逸。

18161319737 发表于 2020-8-10 23:44:23

没办法吧,硬件阻塞了。
而且485的架构,
只有等上一帧接收完成或者超时。

你可以接收完毕就响应。

dukelec 发表于 2020-8-11 00:18:52

本帖最后由 dukelec 于 2020-8-11 00:20 编辑

高級語言都有 queue 可以用的吧,所有命令入 queue,然後只要 queue 有命令就第一時間按照順序下發至總線(下發程序被 queue 中的數據喚醒),並在收到設備回覆的時候喚醒入 queue 者(可以通過另一個 queue 或者信號量之類的東西,或者採用回調的方式)。定時器定時時間到就把命令入 queue,用戶操作按鈕也把命令入 queue。queue 可以使用多線程方式,也可以使用 asyncio 異步方式。反正 Python 這樣操作很簡單。

pursuits 发表于 2020-8-11 09:19:39

点击按钮发送控制命令时,加个定时器是否关闭的判断就行了吧
while(定时器关闭==1)
{
Thread。sleep(1000);
}
发送命令

canspider 发表于 2020-8-11 09:39:25

dukelec 发表于 2020-8-11 00:18
高級語言都有 queue 可以用的吧,所有命令入 queue,然後只要 queue 有命令就第一時間按照順序下發至總線( ...

正解,有UI有后台的多线程都是采用这种Queue的方式来解耦的

不过你这个太复杂了,需要专业的软件开发人员才行
半路出家的嵌入式开发人员还是得想点别的办法

落叶知秋 发表于 2020-8-11 09:40:00

1.通信线路用线程来处理,一直激活,带一个缓冲区,有数据就发,没数据就自己一边玩
2.无论定时器还是用户界面,都通过接口把数据写入缓冲区,等线程完成交互返回结果
3.如果接口不是阻塞的,而是异步返回就比较麻烦,写程序的时候要不断查询结果

w600 发表于 2020-8-11 22:43:13

落叶知秋 发表于 2020-8-11 09:40
1.通信线路用线程来处理,一直激活,带一个缓冲区,有数据就发,没数据就自己一边玩
2.无论定时器还是用户 ...

这位兄弟的回复,让我想到思路了。回头测试下

Error.Dan 发表于 2020-8-11 23:10:58

落叶知秋 发表于 2020-8-11 09:40
1.通信线路用线程来处理,一直激活,带一个缓冲区,有数据就发,没数据就自己一边玩
2.无论定时器还是用户 ...

这个时候就体现出C#的优势了,语言原生的对异步操作有良好支持
实际上我自己写的程序现在到处是随手起task干活(匿名函数用着太爽了),用task类自带的各种附加功能做调度,快速把功能码出来才是最重要的
真正耗时间的操作,要么放到后台去跑,有结果了更新UI数据缓存,或者直接阻塞UI,反正task.wait也好,信号量也好,在C#里面用起来都非常简单

实现这些功能需要一些事件响应式编程基础,这个对习惯了单片机中断的嵌入式工程师来说也不是很难的事情,且不说代码写起来比C爽太多.

我甚至在之前的一个项目里面专门写了一个form只有一个功能就是弹出来显示上面转圈圈的gif,但凡是有阻塞UI时间比较长的操作的就把这个窗口弹出来然后禁用UI,等活干完了再解除掉.实际上那个项目里面因为用户需要手动触发orm做数据库模糊查询,因为有这个功能,我根本不用跟用户去解释为啥UI卡住了.

Error.Dan 发表于 2020-8-11 23:26:45

顺便回一下LZ,做从c#少用定时器,我的程序里面一般只有一个定时器用来定期刷UI,其他的操作从来不用定时器,本来winform里面的定时器就不是用来做精确定时和触发事件的.
你这个问题上面大家已经给了正确的方法了,就是用queue做命令缓存,独立线程操作IO,返回值通过事件函数耦合到状态机或者其他具体功能.另外C#提供了一个线程安全的Queue组件,这个在通信这种可能中断的情况下是非常有用的,基本的queue还是有一定的线程安全风险的,当然如果有控制queue长度的操作可以直接用基本的queue,不用担心因为消费者挂了生产者一直往queue里面塞东西把内存撑爆.或者queue都空了还在继续尝试往外弹数据.
Invoke也少用(不是不用),对性能影响挺大的,如果做过一屏刷新几百个变量的这种程序你就知道直接刷UI是非常痛苦的,把窗体都刷撕裂了还很难保证逻辑上的正确性,对UI建模做buffer才是正确的方法.

jarodzz 发表于 2020-8-12 03:59:27

解決"多条命令同时发送的情况"最基本的方式就是lock,算是resource contention的問題。進階的方式就是 dukelec 提到的 quence。
其實C#中quence的用法也是很簡單。

hy317 发表于 2020-8-12 09:07:11

可以把控制命令单独设置一个缓存区,在定时器发送正常轮询指令前优先发送这个控制命令缓存区的数据就是了

foxpro2005 发表于 2020-9-9 17:31:14

多线程+队列

mylinger 发表于 2020-9-9 18:08:10

多线程+事件,收到数据发布一个事件,然后建个方法订阅这个事件。

sunny_82 发表于 2020-9-10 07:26:16

落叶知秋 发表于 2020-8-11 09:40
1.通信线路用线程来处理,一直激活,带一个缓冲区,有数据就发,没数据就自己一边玩
2.无论定时器还是用户 ...

条理清晰,指导性强!
数据交互线程是同步或者异步对主进程处理其实都差不多的。相当于tcp通信的同步异步模式

shawn_bu 发表于 2020-9-10 16:16:41

简单啊,发送指令的操作放在独立线程里面。这个线程就负责不断查询发送指令队列,如果有就顺序发送。

然后你的定时器里面和用户点击就负责把指令塞到发送队列就可以了。
页: [1]
查看完整版本: C# 串口定时轮询采集中如何插入能及时响应的控制命令