问题描述#
前几天用SerialPort类写一个串口的测试程序,关闭串口的时候会让界面卡死。
参考博客windows程序界面卡死的原因,得出界面卡死原因:主线程和其他的线程由于资源或者锁争夺,出现了死锁。

参考知乎文章WinForm界面假死,如何判断其卡在代码中的哪一步?,通过点击调试暂停,查看ui线程函数栈,直接定位阻塞代码的行数,确定问题出现在SerialPort类的Close()方法。

参考文章C# 串口操作系列(2) – 入门篇,为什么我的串口程序在关闭串口时候会死锁 ?文章的解决方法和网上的大部分解决方法类似:定义2个bool类型的标记Listening和Closing,关闭串口和接受数据前先判断一下。我个人并不太接受这种方法,感觉还有更好的方式,而且文章讲述的也并不太清楚。

查找原因#
基于刨根问底的原则,我继续查找问题发生的原因。
先看看导致界面卡死的代码:

void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)
{   //获取串口读取的字节数int n = comm.BytesToRead;    //读取缓冲数据  comm.Read(buf, 0, n);       //因为要访问ui资源,所以需要使用invoke方式同步ui。   this.Invoke(new Action(() =>{...界面更新,略}));
}   private void buttonOpenClose_Click(object sender, EventArgs e)
{   //根据当前串口对象,来判断操作   if (comm.IsOpen)   {   //打开时点击,则关闭串口   comm.Close();//界面卡死的原因}   else  {...}
}

问题就出现在上面的代码中,原理目前还不明确,我只能参考.NET源码来查找问题。

SerialPort类Open()方法#
SerialPort类Close()方法的源码如下:public void Open(){//省略部分代码...internalSerialStream = new SerialStream(portName, baudRate, parity, dataBits, stopBits, readTimeout,writeTimeout, handshake, dtrEnable, rtsEnable, discardNull, parityReplace);internalSerialStream.SetBufferSizes(readBufferSize, writeBufferSize); internalSerialStream.ErrorReceived += new SerialErrorReceivedEventHandler(CatchErrorEvents);internalSerialStream.PinChanged += new SerialPinChangedEventHandler(CatchPinChangedEvents);internalSerialStream.DataReceived += new SerialDataReceivedEventHandler(CatchReceivedEvents);}

每次执行SerialPort类Open()方法都会出现实例化一个SerialStream类型的对象,并将CatchReceivedEvents事件处理程序绑定到SerialStream实例的DataReceived事件。

SerialStream类CatchReceivedEvents方法的源码如下:

private void CatchReceivedEvents(object src, SerialDataReceivedEventArgs e){SerialDataReceivedEventHandler eventHandler = DataReceived;SerialStream stream = internalSerialStream;if ((eventHandler != null) && (stream != null)){lock (stream) {bool raiseEvent = false;try {raiseEvent = stream.IsOpen && (SerialData.Eof == e.EventType || BytesToRead >= receivedBytesThreshold);    }catch {// Ignore and continue. SerialPort might have been closed already! }finally {if (raiseEvent)eventHandler(this, e);  // here, do your reading, etc. }}}}

可以看到SerialStream类CatchReceivedEvents方法触发自身的DataReceived事件,这个DataReceived事件就是我们处理串口接收数据的用到的事件。

DataReceived事件处理程序是在lock (stream) {…}块中执行的,ErrorReceived 、PinChanged 也类似。

SerialPort类Close()方法#
SerialPort类Close()方法的源码如下:

 // Calls internal Serial Stream's Close() method on the internal Serial Stream.public void Close(){Dispose();}public void Dispose() {Dispose(true);GC.SuppressFinalize(this);}protected override void Dispose( bool disposing ){if( disposing ) {if (IsOpen) {internalSerialStream.Flush();internalSerialStream.Close();internalSerialStream = null;}}base.Dispose( disposing );}      

可以看到,执行Close()方法最终会调用Dispose( bool disposing )方法。
微软SerialPort类对父类的Dispose( bool disposing )方法进行了重写,在执行base.Dispose( disposing )前会执行internalSerialStream.Close()方法,也就是说SerialPort实例执行Close()方法时会先关闭SerialPort实例内部的SerialStream实例,再执行父类的Close()操作。

base.Dispose( disposing )方法不作为重点,我们再看internalSerialStream.Close()方法。

SerialStream类源码没有找到Close()方法,说明没有重写父类的Close方法,直接看父类的Close()方法,源码如下:

public virtual void Close(){Dispose(true);GC.SuppressFinalize(this);}

SerialStream父类的Close方法调用了Dispose(true),不过SerialStream类重写了父类的Dispose(bool disposing)方法,源码如下:

protected override void Dispose(bool disposing){if (_handle != null && !_handle.IsInvalid) {try {//省略一部分代码}finally {// If we are disposing synchronize closing with raising SerialPort eventsif (disposing) {lock (this) {_handle.Close();_handle = null;}}else {_handle.Close();_handle = null;}base.Dispose(disposing);}}}

SerialStream父类的Close方法调用了Dispose(true),上面的代码一定会执行到lock (this) 语句,也就是说SerialStream实例执行Close()方法时会lock自身。

死锁原因#
把我们前面源码分析的结果总结一下:

DataReceived事件处理程序是在lock (stream) {…}块中执行的
SerialPort实例执行Close()方法时会先关闭SerialPort实例内部的SerialStream实例
SerialStream实例执行Close()方法时会lock实例自身
当辅助线程调用DataReceived事件处理程序处理串口数据但还未更新界面python基础教程
时,点击界面“关闭”按钮调用SerialPort实例的Close()方法,UI线程会在lock(stream)处一直等待辅助线程释放stream的线程锁。
当辅助线程处理完数据准备更新界面时问题来了,DataReceived事件处理程序中的this.Invoke()一直会等待UI线程来执行委托,但此时UI线程还停在SerialPort实例的Close()方法处等待DataReceived事件处理程序执行完成。
此时,线程死锁发生,两边都执行不下去了。

解决死锁#
网上大多数方法都是定义2个bool类型的标记Listening和Closing,关闭串口和接受数据前先判断一下。
我的方法是DataReceived事件处理程序用this.BeginInvoke()更新界面,不等待UI线程执行完委托就返回,stream的线程锁会很快释放,SerialPort实例的Close()方法也无需等待。

总结#
问题最终的答案其实很简单,但我在查阅.NET源码查找问题源头的过程中收获了很多。这是我第一次这么深入的查看.NET源码,发现这种解决问题的方法还是很有用处的。结果不重要,解决问题c#教程的方法是最重要的。

作者: time-flies

出处:https://www.cnblogs.com/timefiles/p/CsharpSerialPortDeadlockOnClose.html

C# 串口关闭时主界面卡死原因分析相关推荐

  1. QT主界面卡死崩溃解决(5种方法)

    声明 本文来自转载. 文章作者: 张小飞 文章链接: https://cryfeifei.cn/2020/05/28/qt-zhu-jie-mian-qia-si-de-jie-jue-fang-an ...

  2. mysql删除表崩溃_MySQL在删除表时I/O错误原因分析

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 问题现象 最近使用sysbench测试MySQL,由于测试时间较长,写了一个脚本按prepare->run->cleanup的顺 ...

  3. 在mysql中删除表正确的是什么原因_MySQL在删除表时I/O错误原因分析

    问题现象 最近使用sysbench测试MySQL,由于测试时间较长,写了一个脚本按prepare->run->cleanup的顺序在后台跑着.跑完后察看日志发现一个问题,MySQL服务的错 ...

  4. 当把串口初始化时,程序卡死的问题

    1.屏蔽串口初始化的时候,程序正常 2.当初始化串口程序的时候,程序出现卡死 3.问题的原因: 没有定义串口中断函数 定义串口中断后,程序正常

  5. 【MyBatis使用】mapper.xml 文件内<if test>标签判断参数值不等于null和空 当参数值为 0 时筛选条件失效原因分析(源码探究)

    这个问题有不少小伙伴遇到过,也给出了解决方案,但是没有探究原因,这次读一下源码,看看原因在哪里. 1. 条件失效情况复现 Mapper.xml内的动态SQL如下[伪代码] <select id= ...

  6. 系统发生无故卡死原因分析

    转自:https://my.oschina.net/xiaomu0082/blog/2990388?from=timeline&isappinstalled=0 作者分析了系统无故卡死的原因, ...

  7. 2022年全球及各个国家、地区互联网用户数量,互联网用户占比、上网时长及上网原因分析[图]

    2021年,全球总人口数量达到78亿,互联网用户数量达到48亿人,截止2022年1月,全球互联网用户数量达到49.5亿人,同比增长4%,互联网用户占总人口的62.5%,每个互联网用户平均每天使用互联网 ...

  8. Win10黑屏卡死原因分析--罕见的内核pushlock死锁问题

    此问题已向微软公司反馈,仅供学习参考 这是微软内核的一个Bug.发生在内核函数 MmEnumerateAddressSpaceAndReferenceImages 和 MiCreateEnclave之 ...

  9. java环境变量一闪而过_Java环境变量配置和Tomcat启动时cmd界面一闪而过问题

    Java环境变量配置 计算机 -> 属性 -> 高级系统设置 -> 环境变量 -> 系统变量中配置Java环境变量. 配置JAVA_HOME,属于必配项,如果不配置,直接指定p ...

最新文章

  1. 【UVA】11992 - Fast Matrix Operations(段树模板)
  2. 一文了解迁移学习经典算法
  3. .net通过一般处理程序模拟用户控件数据保持、Ispostback 【第二版将html与ashx文件分开】...
  4. 那些藏在你键盘里的emoji,背后到底有着怎样的故事?
  5. jQuery编写插件
  6. Atitit. 数据约束 校验 原理理论与 架构设计 理念模式java php c#.net js javascript mysql oracle...
  7. firebase连接不上_如何在Firebase上托管Blazor应用程序
  8. 中根遍历二叉查找树所得序列一定是有序序列_数据结构考研学习笔记(九)树、森林...
  9. 详细讲解3DMAX导出插件-tiamo
  10. ajax中加html,向DIV中写入HTML(AJAX高手赐教)
  11. Https下字体文件无法加载的解决方案
  12. c语言复杂程序100例,经典C语言程序100例(1-10)
  13. 什么是DHCP(中继模式)
  14. Z-001 开关电源共模电感计算的方法详解
  15. 主动降噪python_尝试使用Pyadi主动降噪时遇到错误
  16. 三国志战略版:求贤之我要换啥
  17. OutMan——Objective-C中的ARC介绍和block的使用
  18. 35美元的OpenWrt SBC目标板在教育方面应用
  19. 美利财务平台架构演进
  20. 银河麒麟桌面V10SP1版本系统休眠唤醒鼠标键盘失效解决方法

热门文章

  1. 《英语整整折磨了我15年》有感
  2. 315家电选购指南 网购洗衣机注意这几点
  3. 常用的数据分析方法论
  4. 神奇的深度图:复杂的效果,不复杂的原理
  5. 业务中台、数据中台、技术中台到底是什么?
  6. java循环删除集合数据_java中循环的几种方式以及删除集合中的指定数据
  7. 算法:动态规划经典题目
  8. Centos 安装阿里云yum源和EPEL源
  9. 第8章 生成式深度学习
  10. redis数据一致性串行化方案_若何保障mysql和redis之间的数据一致性?(转发)