-
2021-03-21 09:19:00
我已经看到这个帖子了,当我使用gnu.io包(也称为RXTX)时,我关闭serialport时遇到了同样的问题。
这不是最后的答案,而是我发现的替代解决方案的建议。
RXTX在我看来有两个问题,如果不是更多:
>根据您的IDE,您需要放置Mac:RXTXcomm.jar和librxtxSerial.jnilib以及PC:RXTXcomm.jar,IDE或Java代码项目根目录下的rxtxSerial.dll,它从IDE到IDE不同。这里的文档不包括如何执行此操作,并且在不同的IDE中,如NetBeans,IntelliJ,即使我已经在Eclipse和IntelliJ上工作,但还没有NetBeans。它还有其他痛苦的问题。
>根据您的操作系统,即使您得到该程序包并运行,在Windows 8.1中为例,关闭端口时出现问题。唯一的解决方案是重新启动IDE /控制台并重新连接。每次开发项目时,都可以重新启动IDE。
我花了很多时间找到一个解决方案,没有解决方案正确关闭Windows 8.1或更高版本(不知道其他环境关闭端口问题),因为包是旧的,并且支持有限。
因此,我建议你去一个更无头痛的封装,称为JSSC。
这是使用JSSC从串口读取数据的简单说明:
public class Main {
public static void main(String[] args) {
SerialPort serialPort = new SerialPort("COM1");
try {
serialPort.openPort();//Open serial port
serialPort.setParams(9600, 8, 1, 0);//Set params.
byte[] buffer = serialPort.readBytes(10);//Read 10 bytes from serial port
serialPort.closePort();//Close serial port
}
catch (SerialPortException ex) {
System.out.println(ex);
}
}
}
而且,它关闭了出口问题的港口。
注意:这是一个开放的答案,如果有人有相关的经验,请通过编辑答案贡献。
我已经看到有人在互联网上提出这个问题,并且与RXTX总体上有几乎相同的问题,并没有找到可用的RXTX解决方案。
我在Stackoverflow中回答了另一个与之前question相似的人。
我想分享我所知道的知识,对于面临同样问题的人来说,这可能是有用的。它可以使你的一天不那么痛苦。
更多相关内容 -
通过串口关闭计算机 Dlephi实现方法(源代码)
2021-05-11 13:39:19Dlephi通过串口也可以关闭计算机和重启 本源码分客户端和服务端,向大家展示了如何利用串口关机的实现方法,内附有源代码。在如图所示的窗口中,选择串口后就可进行相应操作,请注意要保证所选串口不要被占用。 ... -
MFC打开串口、关闭串口、收发数据接口函数的实现
2018-11-16 13:24:16MFC打开串口、关闭串口接口函数的实现,以及收发数据的接口函数实现 -
通过串口关闭计算机 Dlephi实现方法(源代码)..rar
2019-05-12 16:10:45通过串口关闭计算机 Dlephi实现方法(源代码)..rar -
C#使用串口关闭远程计算机
2021-05-01 05:36:31Visual C#借助串口关闭远程的对方计算机,指定计算机并关闭系统,简单的围绕串口展开编程,适合C#初学者学习一些串口的编程知识。比如打开串口,发送关机命令数据,关闭计算机,取消关机指令等功能,不知道如何执行... -
Android 9 调试串口关闭
2019-09-23 16:18:37背景:项目有用调试串口作为普通口进行前期研修需求 为达到这个目的,需要将原来作为调试用的串口屏蔽: 1.device中将bootargs注释掉(注释后烧写,发现串口log照常输出,所以继续做了第2步) // bootargs = ...背景:项目有用调试串口作为普通口进行前期研修需求
为达到这个目的,需要将原来作为调试用的串口屏蔽:
1.device中将bootargs注释掉(注释后烧写,发现串口log照常输出,所以继续做了第2步)
// bootargs = "vmalloc=480M console=ttyAMA0,115200n8";
2.menuconfig中取消console支持:(按本步取消后烧写,bootloader log照常输出,Android Log不再输出,不影响测试,OK)
-
串口关闭时候报错、死锁、还有数据在处理报串口关闭错误 解决办法
2017-08-11 16:52:00为什么C#串口程序在关闭串口时候会死锁_老文章_赛迪网 http://www.ccidnet.com/2010/0524/2067861.shtml 【赛迪网讯】用过微软SerialPort类的人,都遇到过这个尴尬,关闭串口的时候会让软件死锁。天哪,我...为什么C#串口程序在关闭串口时候会死锁_老文章_赛迪网 http://www.ccidnet.com/2010/0524/2067861.shtml
【赛迪网讯】用过微软SerialPort类的人,都遇到过这个尴尬,关闭串口的时候会让软件死锁。天哪,我可不是武断,算了。不要太绝对了。99.9%的人吧,都遇到过这个问题。我想只有一半的人真的解决了。另外一半的人就睁只眼闭只眼阿弥佗佛希望不要在客户那里出现这问题了。
view plaincopy to clipboardprint? void comm_DataReceived(object sender, SerialDataReceivedEventArgs e) { //先记录下来,避免某种原因,人为的原因, 操作几次之间时间长,缓存不一致 int n = comm.BytesToRead; //声明一个临时数组存储当前来的串口数据 byte[] buf = new byte[n]; //增加接收计数 received_count += n; //读取缓冲数据 comm.Read(buf, 0, n); //清除字符串构造器的内容 builder.Clear(); //因为要访问ui资源,所以需要使用invoke方式同步ui。 this.Invoke((EventHandler)(delegate{...界面更新,略})); } private void buttonOpenClose_Click(object sender, EventArgs e) { //根据当前串口对象,来判断操作 if (comm.IsOpen) { //打开时点击,则关闭串口 comm.Close();//这里就是可能导致软件死掉的地方 } else {...} } void comm_DataReceived(object sender, SerialDataReceivedEventArgs e) { //先记录下来,避免某种原因,人为的原因, 操作几次之间时间长,缓存不一致 int n = comm.BytesToRead; //声明一个临时数组存储当前来的串口数据 byte[] buf = new byte[n]; //增加接收计数 received_count += n; //读取缓冲数据 comm.Read(buf, 0, n); //清除字符串构造器的内容 builder.Clear(); //因为要访问ui资源,所以需要使用invoke方式同步ui。 this.Invoke((EventHandler)(delegate{...界面更新,略})); } private void buttonOpenClose_Click(object sender, EventArgs e) { //根据当前串口对象,来判断操作 if (comm.IsOpen) { //打开时点击,则关闭串口 comm.Close();//这里就是可能导致软件死掉的地方 } else {...} }
为什么会死锁呢,并发冲突。 我们要了解一下SerialPort的实现和串口通讯机制,在你打开串口的时候,SerialPort会创建一个监听线程ListenThread,在这个线程中,等待注册的串口中断,当收到中断后,会调用DataReceived事件。调用完成后,继续进入循环等待,直到串口被关闭退出线程。 我们的UI主线程如何做的呢,首先创建一个窗体,然后执行了Application.Run(窗体实例)。是这样把,这里的Application.Run就是创建了一个消息循环,循环的处理相关的消息。 这里我们就有了2个线程,UI主线程、串口监听线程。那么你在DataReceived处理数据的时候,就需要线程同步,避免并发冲突,什么是并发冲突?并发冲突就是2个或多个并行(至少看上去像)的线程运行的时候,多个线程共同的操作某一线程的资源,在时序上同时或没有按我们的预计顺序操作,这样就可能导致数据混乱无序或是彼此等待完成死锁软件。 而串口程序大多是后者。为什么呢,看看我们的例子中DataReceived做了什么?首先读取数据,然后就是调用this.Invoke方法更新UI了。这里Invoke的时候,监听线程将等待UI线程的标志,等到后,开始操作UI的资源,当操作完成之前,监听线程也就停在DataReceived方法的调用这里,如果这个时候。并发了关闭串口的操作会如何呢?SerialPort的Close方法,会首先尝试等待和监听线程一样的一个互斥体、临界区、或是事件(不确定.net用的哪种)。那这个同步对象什么时候释放呢?每次循环结束就释放,哦。循环为什么不结束呢?因为这一次的循环操作执行到DataReceived之后,执行了Invoke去更新界面了,那Invoke怎么又没有执行完成呢?看上去很简单的几行代码。虽然我没仔细研读过.net的Invoke原理,但我猜测是通过消息的方式来同步的,这也是为什么这么多的类,只有控件(窗体也是控件的一种,.net在概念上,颠覆了微软自己的概念,传统的win32编程,是说所有的控件都是个window,只是父窗体不同,表现形式不同,但都是基于系统消息队列的,.net出于更高的抽象,正好反过来了。呵呵)才有Invoke方法了。(委托自己的Invoke和这个不同) 我猜测控件/窗体的Invoke是SendMessage方式实现的,那么发送消息后就会等待消息循环来处理消息了。如果你直接去关闭串口了。你点击按钮本身也会被转换成消息WM_CLICK,消息循环在处理按钮的WM_CLICK时候,调用你按钮的OnClick方法,进而触发调用你的ButtonClose_Click事件,这都是同步调用的,你的主线程,处理消息的过程,停在了这个Click事件,而你的Click事件又去调用了SerialPort的Close方法,Close方法又因为和串口监听线程的同步信号量关联在一起需要等待一次的while结束,而这个while循环中调用了DataReceived方法,这个方法中调用了Invoke,也就是发送了消息到消息队列等待结果,但消息循环正在处理你的关闭按钮事件等待退出。 实在太复杂了,这个情况下,你想要真的关闭串口成功,就需要while中的DataReceived方法调用结束释放同步信号,就需要执行完Invoke,就需要执行消息循环,幸运的是,我们真的有办法执行消息循环来打破僵局。Application.DoEvents()。还好,不幸中的万幸。可是问题又来了,你能让Invoke结束,但你无法确定是否在你调用消息循环后,你的某一时刻不会再次并发,可能由于单cpu的串行操作模拟并行中,又把时间片先分给了优先级高的串口监听线程呢?是有可能的。所以,我们就需要一点方法来避免再次invoke窗体。优化后不会司机的例子如下,我们修改DataReceived方法,关闭方法,并定义2个标记Listening和Closing。
view plaincopy to clipboardprint? using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.IO.Ports; using System.Text.RegularExpressions; namespace SerialportSample { public partial class SerialportSampleForm : Form { private SerialPort comm = new SerialPort(); private StringBuilder builder = new StringBuilder(); //避免在事件处理方法中反复的创建,定义到外面。 private long received_count = 0;//接收计数 private long send_count = 0;//发送计数 private bool Listening = false; //是否没有执行完invoke相关操作 private bool Closing = false; //是否正在关闭串口,执行Application.DoEvents, 并阻止再次invoke public SerialportSampleForm() { InitializeComponent(); } //窗体初始化 private void Form1_Load(object sender, EventArgs e) { //初始化下拉串口名称列表框 string[] ports = SerialPort.GetPortNames(); Array.Sort(ports); comboPortName.Items.AddRange(ports); comboPortName.SelectedIndex = comboPortName.Items.Count > 0 ? 0 : -1; comboBaudrate.SelectedIndex = comboBaudrate.Items.IndexOf("9600"); //初始化SerialPort对象 comm.NewLine = "\r\n"; comm.RtsEnable = true;//根据实际情况吧。 //添加事件注册 comm.DataReceived += comm_DataReceived; } void comm_DataReceived (object sender, SerialDataReceivedEventArgs e) { if (Closing) return; //如果正在关闭,忽略操作,直接返回, 尽快的完成串口监听线程的一次循环 try { Listening = true; //设置标记,说明我已经开始处理数据, 一会儿要使用系统UI的。 int n = comm.BytesToRead; //先记录下来,避免某种原因,人为的原因, 操作几次之间时间长,缓存不一致 byte[] buf = new byte[n]; //声明一个临时数组存储当前来的串口数据 received_count += n;//增加接收计数 comm.Read(buf, 0, n);//读取缓冲数据 builder.Clear();//清除字符串构造器的内容 //因为要访问ui资源,所以需要使用invoke方式同步ui。 this.Invoke((EventHandler)(delegate { //判断是否是显示为16禁止 if (checkBoxHexView.Checked) { //依次的拼接出16进制字符串 foreach (byte b in buf) { builder.Append(b.ToString("X2") + " "); } } else { //直接按ASCII规则转换成字符串 builder.Append(Encoding.ASCII.GetString(buf)); } //追加的形式添加到文本框末端,并滚动到最后。 this.txGet.AppendText(builder.ToString()); //修改接收计数 labelGetCount.Text = "Get:" + received_count.ToString(); })); } finally { Listening = false;//我用完了,ui可以关闭串口了。 } } private void buttonOpenClose_Click(object sender, EventArgs e) { //根据当前串口对象,来判断操作 if (comm.IsOpen) { Closing = true; while (Listening) Application.DoEvents(); //打开时点击,则关闭串口 comm.Close(); } else { //关闭时点击,则设置好端口,波特率后打开 comm.PortName = comboPortName.Text; comm.BaudRate = int.Parse(comboBaudrate.Text); try { comm.Open(); } catch(Exception ex) { //捕获到异常信息,创建一个新的comm对象, 之前的不能用了。 comm = new SerialPort(); //现实异常信息给客户。 MessageBox.Show(ex.Message); } } //设置按钮的状态 buttonOpenClose.Text = comm.IsOpen ? "Close" : "Open"; buttonSend.Enabled = comm.IsOpen; } //动态的修改获取文本框是否支持自动换行。 private void checkBoxNewlineGet_CheckedChanged (object sender, EventArgs e) { txGet.WordWrap = checkBoxNewlineGet.Checked; } private void buttonSend_Click(object sender, EventArgs e) { //定义一个变量,记录发送了几个字节 int n = 0; //16进制发送 if (checkBoxHexSend.Checked) { //我们不管规则了。如果写错了一些,我们允许的, 只用正则得到有效的十六进制数 MatchCollection mc = Regex.Matches(txSend.Text, @"(?i)[\da-f]{2}"); List<byte> buf = new List<byte>(); //填充到这个临时列表中 //依次添加到列表中 foreach (Match m in mc) { buf.Add(byte.Parse(m.Value)); } //转换列表为数组后发送 comm.Write(buf.ToArray(), 0, buf.Count); //记录发送的字节数 n = buf.Count; } else//ascii编码直接发送 { //包含换行符 if (checkBoxNewlineSend.Checked) { comm.WriteLine(txSend.Text); n = txSend.Text.Length + 2; } else//不包含换行符 { comm.Write(txSend.Text); n = txSend.Text.Length; } } send_count += n;//累加发送字节数 labelSendCount.Text = "Send:" + send_count.ToString(); //更新界面 } private void buttonReset_Click(object sender, EventArgs e) { //复位接受和发送的字节数计数器并更新界面。 send_count = received_count = 0; labelGetCount.Text = "Get:0"; labelSendCount.Text = "Send:0"; } } }
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.IO.Ports; using System.Text.RegularExpressions; namespace SerialportSample { public partial class SerialportSampleForm : Form { private SerialPort comm = new SerialPort(); private StringBuilder builder = new StringBuilder(); //避免在事件处理方法中反复的创建,定义到外面。 private long received_count = 0; //接收计数 private long send_count = 0; //发送计数 private bool Listening = false; //是否没有执行完invoke相关操作 private bool Closing = false; //是否正在关闭串口,执行Application.DoEvents, 并阻止再次invoke public SerialportSampleForm() { InitializeComponent(); } //窗体初始化 private void Form1_Load(object sender, EventArgs e) { //初始化下拉串口名称列表框 string[] ports = SerialPort.GetPortNames(); Array.Sort(ports); comboPortName.Items.AddRange(ports); comboPortName.SelectedIndex = comboPortName.Items.Count > 0 ? 0 : -1; comboBaudrate.SelectedIndex = comboBaudrate.Items.IndexOf("9600"); //初始化SerialPort对象 comm.NewLine = "\r\n"; comm.RtsEnable = true; //根据实际情况吧。 //添加事件注册 comm.DataReceived += comm_DataReceived; } void comm_DataReceived (object sender, SerialDataReceivedEventArgs e) { if (Closing) return; //如果正在关闭,忽略操作,直接返回, 尽快的完成串口监听线程的一次循环 try { Listening = true; //设置标记,说明我已经开始处理数据, 一会儿要使用系统UI的。 int n = comm.BytesToRead; //先记录下来,避免某种原因,人为的原因, 操作几次之间时间长,缓存不一致 byte[] buf = new byte[n]; //声明一个临时数组存储当前来的串口数据 received_count += n; //增加接收计数 comm.Read(buf, 0, n); //读取缓冲数据 builder.Clear(); //清除字符串构造器的内容 //因为要访问ui资源,所以需要使用invoke方式同步ui。 this.Invoke((EventHandler)(delegate { //判断是否是显示为16禁止 if (checkBoxHexView.Checked) { //依次的拼接出16进制字符串 foreach (byte b in buf) { builder.Append(b.ToString("X2") + " "); } } else { //直接按ASCII规则转换成字符串 builder.Append(Encoding.ASCII.GetString(buf)); } //追加的形式添加到文本框末端,并滚动到最后。 this.txGet.AppendText(builder.ToString()); //修改接收计数 labelGetCount.Text = "Get:" + received_count.ToString(); })); } finally { Listening = false;//我用完了,ui可以关闭串口了。 } } private void buttonOpenClose_Click(object sender, EventArgs e) { //根据当前串口对象,来判断操作 if (comm.IsOpen) { Closing = true; while (Listening) Application.DoEvents(); //打开时点击,则关闭串口 comm.Close(); } else { //关闭时点击,则设置好端口,波特率后打开 comm.PortName = comboPortName.Text; comm.BaudRate = int.Parse(comboBaudrate.Text); try { comm.Open(); } catch(Exception ex) { //捕获到异常信息,创建一个新的comm对象, 之前的不能用了。 comm = new SerialPort(); //现实异常信息给客户。 MessageBox.Show(ex.Message); } } //设置按钮的状态 buttonOpenClose.Text = comm.IsOpen ? "Close" : "Open"; buttonSend.Enabled = comm.IsOpen; } //动态的修改获取文本框是否支持自动换行。 private void checkBoxNewlineGet_CheckedChanged (object sender, EventArgs e) { txGet.WordWrap = checkBoxNewlineGet.Checked; } private void buttonSend_Click(object sender, EventArgs e) { //定义一个变量,记录发送了几个字节 int n = 0; //16进制发送 if (checkBoxHexSend.Checked) { //我们不管规则了。如果写错了一些, //我们允许的,只用正则得到有效的十六进制数 MatchCollection mc = Regex.Matches(txSend.Text, @"(?i)[\da-f]{2}"); List<byte> buf = new List<byte>();//填充到这个临时列表中 //依次添加到列表中 foreach (Match m in mc) { buf.Add(byte.Parse(m.Value)); } //转换列表为数组后发送 comm.Write(buf.ToArray(), 0, buf.Count); //记录发送的字节数 n = buf.Count; } else//ascii编码直接发送 { //包含换行符 if (checkBoxNewlineSend.Checked) { comm.WriteLine(txSend.Text); n = txSend.Text.Length + 2; } else//不包含换行符 { comm.Write(txSend.Text); n = txSend.Text.Length; } } send_count += n; //累加发送字节数 labelSendCount.Text = "Send:" + send_count.ToString(); //更新界面 } private void buttonReset_Click(object sender, EventArgs e) { //复位接受和发送的字节数计数器并更新界面。 send_count = received_count = 0; labelGetCount.Text = "Get:0"; labelSendCount.Text = "Send:0"; } } }
至此,不会再出现关闭死锁问题了。 希望这篇文章能解你的燃眉之急,非常高兴能与读者分享我层遇到,大多数人都遇到的这个问题。如果说的不明白,欢迎讨论。 后续的有关通讯程序底层设计的文章会讲述一个具有丰富扩展性,但有设计简介的万能通讯库,支持网络、蓝牙、串口通讯、并口通讯。但不要指望我都实现出来了,我只是设计出这个框架。
-
c#:串口知识点和串口关闭死机问题
2020-12-10 19:45:41(1)C# 获取本机的串口号 using System.IO.Ports;... //获取本机串口名称,存入PortNames数组中 for (int i = 0; i < PortNames.Count(); i++) { cbxSerilPort.Items.Add(PortNames[i]); //将数...(1)C# 获取本机的串口号
using System.IO.Ports; //头文件
string[] PortNames = SerialPort.GetPortNames(); //获取本机串口名称,存入PortNames数组中
for (int i = 0; i < PortNames.Count(); i++)
{
cbxSerilPort.Items.Add(PortNames[i]); //将数组内容加载到comboBox控件中
}
显然,我们需要定义一个SerialPort对象。添加DataReceived事件响应收到数据,还有一个重点,我们需要记得设置NewLine属性哦。好想有的版本不设置的时候,WriteLine和Write效果一样。
所以,我们需要初始化SerialPort对象,例如:
- //初始化SerialPort对象
- comm.NewLine = "/r/n";
- comm.RtsEnable = true;//根据实际情况吧。
- //添加事件注册
- comm.DataReceived += comm_DataReceived;
(2)串口关闭死机问题
第一篇文章我相信很多人不看都能做的出来,但是,用过微软SerialPort类的人,都遇到过这个尴尬,关闭串口的时候会让软件死锁。天哪,我可不是武断,算了。不要太绝对了。99.9%的人吧,都遇到过这个问题。我想只有一半的人真的解决了。另外一半的人就睁只眼闭只眼阿弥佗佛希望不要在客户那里出现这问题了。
你看到我的文章,就放心吧,这问题有救了。我们先回顾一下上一篇中的代码
[c-sharp] view plain copy
- void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)
- {
- //先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致
- int n = comm.BytesToRead;
- //声明一个临时数组存储当前来的串口数据
- byte[] buf = new byte[n];
- //增加接收计数
- received_count += n;
- //读取缓冲数据
- comm.Read(buf, 0, n);
- //清除字符串构造器的内容
- builder.Clear();
- //因为要访问ui资源,所以需要使用invoke方式同步ui。
- this.Invoke((EventHandler)(delegate{...界面更新,略}));
- }
- private void buttonOpenClose_Click(object sender, EventArgs e)
- {
- //根据当前串口对象,来判断操作
- if (comm.IsOpen)
- {
- //打开时点击,则关闭串口
- comm.Close();//这里就是可能导致软件死掉的地方
- }
- else
- {...}
- }
为什么会死锁呢,并发冲突。
我们要了解一下SerialPort的实现和串口通讯机制,在你打开串口的时候,SerialPort会创建一个监听线程ListenThread,在这个线程中,等待注册的串口中断,当收到中断后,会调用DataReceived事件。调用完成后,继续进入循环等待,直到串口被关闭退出线程。
我们的UI主线程如何做的呢,首先创建一个窗体,然后执行了Application.Run(窗体实例)。是这样把,这里的Application.Run就是创建了一个消息循环,循环的处理相关的消息。
这里我们就有了2个线程,UI主线程、串口监听线程。那么你在DataReceived处理数据的时候,就需要线程同步,避免并发冲突,什么是并发冲突?并发冲突就是2个或多个并行(至少看上去像)的线程运行的时候,多个线程共同的操作某一线程的资源,在时序上同时或没有按我们的预计顺序操作,这样就可能导致数据混乱无序或是彼此等待完成死锁软件。
而串口程序大多是后者。为什么呢,看看我们的例子中DataReceived做了什么?首先读取数据,然后就是调用this.Invoke方法更新UI了。这里Invoke的时候,监听线程将等待UI线程的标志,等到后,开始操作UI的资源,当操作完成之前,监听线程也就停在DataReceived方法的调用这里,如果这个时候。并发了关闭串口的操作会如何呢?SerialPort的Close方法,会首先尝试等待和监听线程一样的一个互斥体、临界区、或是事件(不确定.net用的哪种)。那这个同步对象什么时候释放呢?每次循环结束就释放,哦。循环为什么不结束呢?因为这一次的循环操作执行到DataReceived之后,执行了Invoke去更新界面了,那Invoke怎么又没有执行完成呢?看上去很简单的几行代码。虽然我没仔细研读过.net的Invoke原理,但我猜测是通过消息的方式来同步的,这也是为什么这么多的类,只有控件(窗体也是控件的一种,.net在概念上,颠覆了微软自己的概念,传统的win32编程,是说所有的控件都是个window,只是父窗体不同,表现形式不同,但都是基于系统消息队列的,.net出于更高的抽象,正好反过来了。呵呵)才有Invoke方法了。(委托自己的Invoke和这个不同)
我猜测控件/窗体的Invoke是SendMessage方式实现的,那么发送消息后就会等待消息循环来处理消息了。如果你直接去关闭串口了。你点击按钮本身也会被转换成消息WM_CLICK,消息循环在处理按钮的WM_CLICK时候,调用你按钮的OnClick方法,进而触发调用你的ButtonClose_Click事件,这都是同步调用的,你的主线程,处理消息的过程,停在了这个Click事件,而你的Click事件又去调用了SerialPort的Close方法,Close方法又因为和串口监听线程的同步信号量关联在一起需要等待一次的while结束,而这个while循环中调用了DataReceived方法,这个方法中调用了Invoke,也就是发送了消息到消息队列等待结果,但消息循环正在处理你的关闭按钮事件等待退出。
实在太复杂了,这个情况下,你想要真的关闭串口成功,就需要while中的DataReceived方法调用结束释放同步信号,就需要执行完Invoke,就需要执行消息循环,幸运的是,我们真的有办法执行消息循环来打破僵局。Application.DoEvents()。还好,不幸中的万幸。可是问题又来了,你能让Invoke结束,但你无法确定是否在你调用消息循环后,你的某一时刻不会再次并发,可能由于单cpu的串行操作模拟并行中,又把时间片先分给了优先级高的串口监听线程呢?是有可能的。所以,我们就需要一点方法来避免再次invoke窗体。优化后不会司机的例子如下,我们修改DataReceived方法,关闭方法,并定义2个标记Listening和Closing。
[c-sharp] view plain copy
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.Linq;
- using System.Text;
- using System.Windows.Forms;
- using System.IO.Ports;
- using System.Text.RegularExpressions;
- namespace SerialportSample
- {
- public partial class SerialportSampleForm : Form
- {
- private SerialPort comm = new SerialPort();
- private StringBuilder builder = new StringBuilder();//避免在事件处理方法中反复的创建,定义到外面。
- private long received_count = 0;//接收计数
- private long send_count = 0;//发送计数
- private bool Listening = false;//是否没有执行完invoke相关操作
- private bool Closing = false;//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke
- public SerialportSampleForm()
- {
- InitializeComponent();
- }
- //窗体初始化
- private void Form1_Load(object sender, EventArgs e)
- {
- //初始化下拉串口名称列表框
- string[] ports = SerialPort.GetPortNames();
- Array.Sort(ports);
- comboPortName.Items.AddRange(ports);
- comboPortName.SelectedIndex = comboPortName.Items.Count > 0 ? 0 : -1;
- comboBaudrate.SelectedIndex = comboBaudrate.Items.IndexOf("9600");
- //初始化SerialPort对象
- comm.NewLine = "/r/n";
- comm.RtsEnable = true;//根据实际情况吧。
- //添加事件注册
- comm.DataReceived += comm_DataReceived;
- }
- void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)
- {
- if (Closing) return;//如果正在关闭,忽略操作,直接返回,尽快的完成串口监听线程的一次循环
- try
- {
- Listening = true;//设置标记,说明我已经开始处理数据,一会儿要使用系统UI的。
- int n = comm.BytesToRead;//先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致
- byte[] buf = new byte[n];//声明一个临时数组存储当前来的串口数据
- received_count += n;//增加接收计数
- comm.Read(buf, 0, n);//读取缓冲数据
- builder.Clear();//清除字符串构造器的内容
- //因为要访问ui资源,所以需要使用invoke方式同步ui。
- this.Invoke((EventHandler)(delegate
- {
- //判断是否是显示为16禁止
- if (checkBoxHexView.Checked)
- {
- //依次的拼接出16进制字符串
- foreach (byte b in buf)
- {
- builder.Append(b.ToString("X2") + " ");
- }
- }
- else
- {
- //直接按ASCII规则转换成字符串
- builder.Append(Encoding.ASCII.GetString(buf));
- }
- //追加的形式添加到文本框末端,并滚动到最后。
- this.txGet.AppendText(builder.ToString());
- //修改接收计数
- labelGetCount.Text = "Get:" + received_count.ToString();
- }));
- }
- finally
- {
- Listening = false;//我用完了,ui可以关闭串口了。
- }
- }
- private void buttonOpenClose_Click(object sender, EventArgs e)
- {
- //根据当前串口对象,来判断操作
- if (comm.IsOpen)
- {
- Closing = true;
- while (Listening) Application.DoEvents();
- //打开时点击,则关闭串口
- comm.Close();
- Closing = false;
- }
- else
- {
- //关闭时点击,则设置好端口,波特率后打开
- comm.PortName = comboPortName.Text;
- comm.BaudRate = int.Parse(comboBaudrate.Text);
- try
- {
- comm.Open();
- }
- catch(Exception ex)
- {
- //捕获到异常信息,创建一个新的comm对象,之前的不能用了。
- comm = new SerialPort();
- //现实异常信息给客户。
- MessageBox.Show(ex.Message);
- }
- }
- //设置按钮的状态
- buttonOpenClose.Text = comm.IsOpen ? "Close" : "Open";
- buttonSend.Enabled = comm.IsOpen;
- }
- //动态的修改获取文本框是否支持自动换行。
- private void checkBoxNewlineGet_CheckedChanged(object sender, EventArgs e)
- {
- txGet.WordWrap = checkBoxNewlineGet.Checked;
- }
- private void buttonSend_Click(object sender, EventArgs e)
- {
- //定义一个变量,记录发送了几个字节
- int n = 0;
- //16进制发送
- if (checkBoxHexSend.Checked)
- {
- //我们不管规则了。如果写错了一些,我们允许的,只用正则得到有效的十六进制数
- MatchCollection mc = Regex.Matches(txSend.Text, @"(?i)[/da-f]{2}");
- List<byte> buf = new List<byte>();//填充到这个临时列表中
- //依次添加到列表中
- foreach (Match m in mc)
- {
- buf.Add(byte.Parse(m.Value));
- }
- //转换列表为数组后发送
- comm.Write(buf.ToArray(), 0, buf.Count);
- //记录发送的字节数
- n = buf.Count;
- }
- else//ascii编码直接发送
- {
- //包含换行符
- if (checkBoxNewlineSend.Checked)
- {
- comm.WriteLine(txSend.Text);
- n = txSend.Text.Length + 2;
- }
- else//不包含换行符
- {
- comm.Write(txSend.Text);
- n = txSend.Text.Length;
- }
- }
- send_count += n;//累加发送字节数
- labelSendCount.Text = "Send:" + send_count.ToString();//更新界面
- }
- private void buttonReset_Click(object sender, EventArgs e)
- {
- //复位接受和发送的字节数计数器并更新界面。
- send_count = received_count = 0;
- labelGetCount.Text = "Get:0";
- labelSendCount.Text = "Send:0";
- }
- }
- }
-
C# 串口关闭时主界面卡死原因分析
2020-12-14 13:35:47前几天用SerialPort类写一个串口的测试程序,关闭串口的时候会让界面卡死。 参考博客windows程序界面卡死的原因,得出界面卡死原因:主线程和其他的线程由于资源或者锁争夺,出现了死锁。 参考知乎文章WinForm界面假... -
labveiw串口关闭
2014-01-22 00:38:00很很用的一个LABVIEW串口关闭程序,非非常好用好用 -
关闭Linux中的串口打印
2021-05-14 17:45:33为了保证串口通信时不能有控制台发出的消息,需要关闭打印。在测试过程中发现,有三种类型的打印,一是uboot的打印,在Starting kernel ...之前的打印都是;二是prink的打印,linux kernel不能用printf,对应的输出... -
windows串口监控
2018-10-08 16:05:43这是我找到的比较好用的串口监视工具,对于调试串口代码有极大的帮助,尤其是在不具备debug条件的情景下。亲测,此工具在win7环境下运行良好。 -
CSerialPort类,彻底关闭串口
2015-01-10 10:27:57自己在前人的基础上更改,增加了线程关闭功能,能够彻底关闭串口,数据多次发送都可,串口使用完毕后可关闭 -
C#串口关闭SerialPort.Close()导致的卡死
2019-02-19 17:13:03上面的链接给出了解决方案: 将Invoke变为BeginInvoke。 亲测可行。 下面还有一篇关于串口相关的介绍: C# 串口操作系列(2) – 入门篇,为什么我的串口程序在关闭串口时候会死锁 ? ... -
Android代码-安卓串口示例,打开、接收、发送、关闭
2019-08-06 05:46:55AndroidSerialPortSample 安卓串口示例,打开、接收、发送、关闭 来自:android-serialport-api 本人把项目移植到 android studio 直接下载: 点我下载 测试的话,需要有自己开发板,现在一般手机都没有预留的串口 -
通过串口关闭对方计算机
2009-04-03 14:01:10通过串口关闭对方计算机,是你学习串口知识的比较好的范类 -
如何用程序关闭一个已经打开的串口
2021-05-19 13:19:29已结贴√问题点数:20回复次数:4 如何用程序关闭一个已经打开的串口调用 C++ 的API 封装了一个串口函数 只能正常打开串口关闭串口想请教下如果串口已经被打开,我如何在程序里判断,并把他关闭 ,然后在打开#include ... -
QT5自制串口助手(3)设置与打开关闭串口
2020-11-17 16:01:23上一章添加的控件你可能发现在代码里并找不到,其实它们被添加在 打开就会发现所有已添加的控件 ...我们只要在这个函数里面完成串口打开与关闭就可以了。 突然发现忘记加波特率选择了。。。改一下 不同选 -
MATLAB 串口系列(一、串口的打开和关闭)
2021-11-10 11:14:23MATLAB经常用于和单片机的串口进行通信,简单记录下MATLAB串口的打开和关闭。MATLAB版本为比较新的2021a,使用serialport函数进行打开操作,关闭则用delete。 % 定义串口参数 serialComName = 'COM38'; ... -
C#串口关闭Close()-线程卡死-解决方法
2017-05-09 15:15:00设置isReceiving 标志位,判断串口读取线程是否在占用资源。 2.设置读写超时,防止卡死在Read()命令里本人比较懒, 在代码关键修改位置标了/!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!/,供大家参考。希望能帮到... -
高级串口编程—C/C++打开串口、关闭串口和读串口函数
2019-02-27 09:25:05最近在做一个陀螺仪的原始数据读取遇到了串口数据读取问题,在网上查阅了一篇技术文档,觉得写的不错,分享一下! 1.1前言 通常使用以下函数来通过Windows系统来对外围设备进行通信,做串口方面的程序,使用... -
C# 解决串口关闭时程序卡死问题
2016-06-01 11:49:26关于串口关闭原理的资料:http://blog.csdn.net/wuyazhe/article/details/5606276 个人使用解决方案,将Invoke变为BeginInvoke。 -
Linux下串口通信详解(下)读写串口及关闭串口
2020-12-03 18:35:53这一篇我将要把读写串口及串口关闭的操作详细介绍一下。 读串口 读串口就是接收串口数据,通过read来实现。 read函数原型: #include <unistd.h> ssize_t read(int fd, void *buf, size_t ... -
解决了关闭串口时死锁的CSerialPort类
2016-12-20 10:09:26与2016-12-20日在其基础上修改了其中关串口卡死现象,添加十六进制与ASCII之间互转函数,觉得蛮好用的推荐给大家 如需转载请标明原始出处:http://blog.csdn.NET/itas109 QQ技术交流群:129518033 这是一份优秀的类... -
Labview读取数据的多串口同时完全关闭
2019-08-04 23:10:19原因是:上一次数据采集的串口没有完全关闭,特别是针对于多串口关闭时又具有一定的操作复杂性 解决: 并行循环 循环1内定义停止按钮,当停止按钮按下时内部的所有循环停止, 并将该停止按钮创建局部变量,在... -
C#中如何安全的关闭串口
2020-07-28 16:49:25VC#中如果涉及到多线程,特别是大量的数据处理和界面更新时,如果简单强制的关闭串口,很可能会造成串口死掉。 串口无法关闭的原因是:要关闭串口的时候,有其它线程还在读取数据或者更新界面。 关键是:在准备关闭... -
C# vs2005串口关闭对方计算机
2008-07-03 15:14:49C# vs2005串口关闭对方计算机 -
labview串口打开之后visaclose不起作用关闭不了老是占用的问题
2021-10-06 09:57:19labview串口打开之后visaclose不起作用关闭不了老是占用的问题 最近在做一个数据接收的上位机,用串口发送接收数据,之前有用过labview,整体操作相当简单,于是就打算用labview做一个。 但是之前做labview的时候就... -
如何安全关闭串口
2016-07-22 15:00:36串口无法关闭的原因是:要关闭串口的时候,有其它线程还在读取数据或者更新界面。 关键是:在准备关闭串口的时候,看看是否在接收和处理数据,如果是就等它处理完为止;在事件处理的最前面,判断如果是准备关闭串口...