SUPER_CRJ 发表于 2020-6-27 12:09:44

C# HIDSharp类中的异步接收AsyncCallback数据处理全局变量异常

本帖最后由 SUPER_CRJ 于 2020-6-27 14:03 编辑

RT。

2020年6月27日14:1:43修改:增加:发现比较奇怪,在每ms中处理IAP数据增加了一句用于测试30K数据指针:textBox4.AppendText( (pAppBinFileRead/48).ToString() + "\r\n" );,就正常了。原因不明。

最近做HID通信,基本都通信了,但是在做:STM32IAP升级的时候,因为数据量相对比较大(30KB数据大小)。
发现了一些问题,最终定位到接收这里:HID Sharp的接收使用 AsyncCallback委托 处理,里面用于接收数据。这些数据是全局变量,然后在一个定时器中进行处理。

发现:
1:如果用单步调试接收数据正常(HID是一发一收的,断点打在上位机接收处理函数里面)。
2:如果自由运行:会出现,数据处理不到位,表现为:上位机已接收到数据,但是没有处理。
3:如果自由运行:处理的全局变量(循环缓冲区)在跳跃(传输30K预计10S,但是2s就走完了。)

HID异步处理方法:参考的是我之前发的一个帖子的5楼:https://www.amobbs.com/thread-5731414-1-1.html
//3 设置异步读数据
                        StateObject stateobj = new StateObject();
                        stateobj.hidstream = gHidStream;

                        gHidStream.BeginRead(stateobj.buffer, 0, StateObject.BUFFSIZE,
                            new AsyncCallback(ReadCallBack),
                            stateobj);

异步处理数据方法(就是把接收的数据存入循环缓冲区):
public void ReadCallBack(IAsyncResult ar)
      {
            StateObject state = (StateObject)ar.AsyncState;
            HidStream handler = state.hidstream;
            int bytesread = 0;
            try
            {
                if (handler.CanRead)
                {
                  bytesread = handler.EndRead(ar);
                  if (bytesread > 0)
                  {
                        for (int i = 1; i < bytesread; i++)
                        { // 拷贝数据
                        // 经过测试:HIDSharp每次发送64字节,实际需要65个大小数组,其第一个似乎没有作用。
                        // 同样的:HID接收的时候,实际也是需要65字节,但是应该是:1开始,似乎都是有效数据。
                        // 系统只做这一件事情!
                            receData.buf = state.buffer;
                            receData.pWrite %= COMBUF_MAXBUFF_SIZE;
                        }
                  }
                }
            }
            catch (Exception err)
            {
                if (err.Message.Contains("Operation failed early"))
                {
                  //MessageBox.Show("设备已拔出!");
                  hidDeviceExceptionConnect = true; // 表示异常断开。
                  tim_autoReconnect.Enabled = true; // 这个操作好像无效。
                  { // 执行正常的操作,但是不清除HIDDev设备,等待操作重连。
                      // 下面操作,其中的某些部分。好像也没有效果。
                        gHidStream.Close();
                        gHidStream.Dispose();
                        btn_openDev.Text = "打开通信口";
                        lb_devStatus.Text = str_arr_deviceComStatus;
                        cmb_deviceList.Enabled = true;
                        btn_refreashDev.Enabled = true;
                  }
                  return;
                }
                else if (err.Message.Contains("Closed"))
                {
                  HIDDev = null;
                  return;
                }
            }
            try
            {
                Array.Clear(state.buffer, 0, StateObject.BUFFSIZE);
                handler.BeginRead(state.buffer, 0, StateObject.BUFFSIZE,
                  new AsyncCallback(ReadCallBack),
                  state);
            }
            catch (Exception err)
            {
                MessageBox.Show(err.Message);
            }
      }

最后就是开了一个定时器处理数据。
private void timProcess_Tick(object sender, EventArgs e)
      { // 调试器暂时采用的方式:使用定时器每ms进行处理
            tim_Process.Enabled = false;    // 发现了一个可能的问题,就是定时器会多次执行副本,所以必须停止。论坛给的解释是相当于回调函数,每次来事件的时候都会进行执行。
            hidComProcess();
            tim_Process.Enabled = true;
      }

上传代码附件:

Error.Dan 发表于 2020-6-27 12:32:23

PC编程搞什么环形缓冲区,预估一下数据量写个上限值然后开buffer往里灌就是了.
你这里的的30k数据直接开一个list然后写死list.count<3M,收完了慢慢处理(实际上是瞬间处理),性能上肯定是没问题的,所以适当的莽上去,不要写复杂的东西.

还有老哥,你这程序很多都是C的写法,在C#里头是比较吃亏的,当然功能实现了就行了,但是C#的优势还是没发挥出来.
还有就是你那个每ms调用太恐怖了,桌面操作系统没有这么好的实时性的,一般都靠异步实现,反正内存足够大,buffer了以后慢慢去处理.
Windows下低于50ms的定时器是没有意义的,但是你100ms又能产生多少数据呢?真让CPU去处理也就是一瞬间的事情.

SUPER_CRJ 发表于 2020-6-27 13:02:04

Error.Dan 发表于 2020-6-27 12:32
PC编程搞什么环形缓冲区,预估一下数据量写个上限值然后开buffer往里灌就是了.
你这里的的30k数据直接开一个 ...

是的。
C#都是网上找资料学的,一直想找个专业培训的,但是几乎都是教的C++,就将就用了。
单次的数据都是:32字节。一问一答,开大了也没用。

SUPER_CRJ 发表于 2020-6-27 13:16:02

Error.Dan 发表于 2020-6-27 12:32
PC编程搞什么环形缓冲区,预估一下数据量写个上限值然后开buffer往里灌就是了.
你这里的的30k数据直接开一个 ...

那我觉得:为什么单步调试为什么正常,但是直接运行出来就有问题了。到底是什么原因产生的?
之前一直用的串口,DataReceivd事件,然后每ms处理,升级一直没有问题。

wye11083 发表于 2020-6-27 18:12:21

Error.Dan 发表于 2020-6-27 12:32
PC编程搞什么环形缓冲区,预估一下数据量写个上限值然后开buffer往里灌就是了.
你这里的的30k数据直接开一个 ...

现在的cpu都能支持1ms中断唤醒的(定时器最小还是15ms间隔)。

但说句实话,我用usb转uart收16mbps连续帧数据大概率丢帧(1帧约700b),哪怕缓冲区开到1mb都没用,windows的usb异步模式稳定性太烂了。

Error.Dan 发表于 2020-6-28 13:25:37

SUPER_CRJ 发表于 2020-6-27 13:16
那我觉得:为什么单步调试为什么正常,但是直接运行出来就有问题了。到底是什么原因产生的?
之前一直用 ...

主要还是多线程的问题,单步模式下你加了断点的程序运行起来肯定比其他托管的线程要慢的多,换言之当你有线程间交互又没有加锁的时候(依靠时序耦合)整个程序在时许上就不是和实际运行一模一样了,这个时候debug只能看看逻辑上的问题。
但是~
但是,框架不会有问题,有问题首先怀疑自己的逻辑,其次是使用姿势不对,最后才是真的可能踩到某些坑里。

还有回答一下你上面那个1ms的定时器和32个字节的关系,这俩没关系,或者说无所谓。
不要说32个字节,就是32K字节不做复杂操作给现代CPU去搞个基本的搬运或者简单运算,1ms绝对干完了,但是系统什么时候给这个1ms给你的程序就不知道了,所以不用担心干不完,存起来一把梭就行了。
C#提倡异步处理,异步处理有益于让系统(也就是程序写的很好的那帮人)去主动调度来协调用户程序(也就是写的没那么好的一帮人),这样总体上效率是高的。

还有你上面补充的那个信息,我不知道你这句话补在哪了。但是你这个是操作UI界面的,本质上是阻塞线程的(虽然非常快,但是也会阻塞,因为等UI的循环过去)。基本上能猜到问题了。
顺便讲一下看起来你这里开的是WinForm的定时器(否则你咋更新的textbox),这个UI用的定时器不准的,而且写C#真的不推荐用定时器(除了真的周期事件),哪怕用事件链做都比定时器要好。
还有就是,尝试一下对界面建模和使用数据绑定,补课一下MVC理论,不要在程序控制流程去刷UI,很慢而且容易崩,更主要的是这样的程序日后维护太痛苦。

随时写了一个用线程定时器的程序,验证了一下定时器最小间隔15ms
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace TimerTest
{
    class Program
    {
      static void Main(string[] args)
      {
            Console.WriteLine("Hello World!");
            Queue<long> queue = new Queue<long>();
            int sum = 0;
            Stopwatch sw = new Stopwatch();

            Timer timer = new Timer((obj) => {
                queue.Enqueue(sw.ElapsedMilliseconds);
            });
            Task task = new Task(() => {
                while(true)
                {
                  if(queue.Count < 5)
                  {
                        Thread.Sleep(2);
                  }
                  else
                  {
                        
                        var num = queue.Count;
                        for (int i = 0; i < num; i++)
                        {
                            Console.WriteLine($"{sw.ElapsedMilliseconds}:{ queue.Dequeue()}#{num}!{sum}");
                        }

                        if(sum++ > 99)
                        {
                            sw.Stop();
                            break;
                        }

                  }
                }
            });
            task.Start();
            sw.Start();
            timer.Change(100, 1);
            Console.ReadKey();
      }
    }
}

但是我仍然不会用定时器去驱动我的程序逻辑的~

Error.Dan 发表于 2020-6-28 13:27:08

wye11083 发表于 2020-6-27 18:12
现在的cpu都能支持1ms中断唤醒的(定时器最小还是15ms间隔)。

但说句实话,我用usb转uart收16mbps连续 ...

怎么看都是USB驱动的锅吧,驱动和Windows之间,Windows才是被插的那个,插出问题了还要被骂,太惨了~

cat3902982 发表于 2020-6-28 22:09:43

请教个问题,出现了“线程间操作无效: 从不是创建控件“textBox1”的线程访问它。”这个怎么解决?

wudicgi 发表于 2020-6-28 22:55:57

cat3902982 发表于 2020-6-28 22:09
请教个问题,出现了“线程间操作无效: 从不是创建控件“textBox1”的线程访问它。”这个怎么解决?
...

有个简单粗暴的办法,先定义个方法:
public void ThreadSafeDelegate(MethodInvoker method)
{
    if (InvokeRequired)
    {
      BeginInvoke(method);
    }
    else
    {
      method.Invoke();
    }
}

然后需要在其他线程中访问 UI 控件时,这样写:
ThreadSafeDelegate(delegate
{
    textBox1.Text = "test";
});

cat3902982 发表于 2020-6-29 10:29:48

wudicgi 发表于 2020-6-28 22:55
有个简单粗暴的办法,先定义个方法:




谢谢,可以解决问题。
页: [1]
查看完整版本: C# HIDSharp类中的异步接收AsyncCallback数据处理全局变量异常