在串行端口的 DataReceived 事件处理程序中读取数据时尝试显示文本时出现死锁
Deadlock when trying to display text while data read in DataReceived event handler of serialport
- 我正在 PC 和嵌入式设备之间实施串行协议。
- 我正在将文件内容从 PC 分块发送到设备。
- 在我开始发送文件之前,我发送了它的名称和大小,然后等待来自设备的 ACK,然后开始循环发送块并等待每个块的确认。
- 我正在使用事件将 DataReceived 线程中接收到的 ACK 与发送数据块的主线程同步。
问题:
在我添加这一行之前,上述所有工作都很好:
textBoxLog.Invoke((MethodInvoker)(() => textBoxLog.AppendText(str)));
所以我可以向用户更新发送进度,当这一行在代码中时,主线程中没有接收到事件并且程序处于死锁状态。
知道为什么吗?
这是代码的简短摘要:
public partial class MainForm : Form
{
AutoResetEvent waitHandle_1 = new AutoResetEvent(false);
AutoResetEvent waitHandle_2 = new AutoResetEvent(false);
public MainForm()
{
InitializeComponent();
try
{
myPort = new System.IO.Ports.SerialPort("COM37");
myPort.BaudRate = 460800;
myPort.Parity = System.IO.Ports.Parity.None;
myPort.DataBits = 8;
myPort.StopBits = System.IO.Ports.StopBits.One;
myPort.Handshake = System.IO.Ports.Handshake.None;
myPort.ReadTimeout = 5000;
myPort.DataReceived += new SerialDataReceivedEventHandler(SerialPortDataReceived);
if (myPort.IsOpen == false) //if not open, open the port
myPort.Open();
}
catch (Exception ex)
{
Console.Write(ex.Message);
}
}
// Serial port received event handler
private void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
{
while (myPort.BytesToRead > 0)
{
var count = myPort.BytesToRead;
var bytes = new byte[count];
myPort.Read(bytes, 0, count);
AddResponseBytes(bytes);
}
}
List<byte> Data = new List<byte>();
private void AddResponseBytes(byte[] bytes)
{
// Analyzing message byte, wait for full message
ProcessResponse(responseData, msgType);
}
private void ProcessResponse(List<byte> bytes, mixCommandType responseType)
{
String str;
switch (responseType)
{
case CommandType.PC_TO_DEVICE_RESPONSE:
str = "Test text";
waitHandle_1.Set();
textBoxLog.Invoke((MethodInvoker)(() => textBoxLog.AppendText(str)));
break;
case CommandType.CHUNK_ACK:
waitHandle_2.Set();
break;
}
}
private void buttonSendFile_Click(object sender, EventArgs e)
{
// Open file and send message to device with file size and wait for ACK
waitHandle_1.WaitOne();
Console.WriteLine("Got response - Continue to send");
uint i = 0;
while (i < bytes.Length)
{
//Send chunk of the file to the device and whait for chunk ack
waitHandle_2.WaitOne();
}
}
}
那是因为 Invoke
将你传递给 UI (主)线程的任何东西分派(你可能已经知道),但是你的 UI 线程已经很忙了,它是在 buttonSendFile_Click
此处被屏蔽:
waitHandle_1.WaitOne();
或此处
waitHandle_2.WaitOne();
Invoke
也在阻塞调用 - 它不会 return 直到调度完成 - 所以你有经典的死锁。
一般来说,阻塞 UI 线程是不好的,尤其是在等待句柄上。要解决您的问题 - 使用后台线程执行您现在在 buttonSendFile_Click
处理程序中执行的操作。
- 我正在 PC 和嵌入式设备之间实施串行协议。
- 我正在将文件内容从 PC 分块发送到设备。
- 在我开始发送文件之前,我发送了它的名称和大小,然后等待来自设备的 ACK,然后开始循环发送块并等待每个块的确认。
- 我正在使用事件将 DataReceived 线程中接收到的 ACK 与发送数据块的主线程同步。
问题: 在我添加这一行之前,上述所有工作都很好:
textBoxLog.Invoke((MethodInvoker)(() => textBoxLog.AppendText(str)));
所以我可以向用户更新发送进度,当这一行在代码中时,主线程中没有接收到事件并且程序处于死锁状态。
知道为什么吗?
这是代码的简短摘要:
public partial class MainForm : Form
{
AutoResetEvent waitHandle_1 = new AutoResetEvent(false);
AutoResetEvent waitHandle_2 = new AutoResetEvent(false);
public MainForm()
{
InitializeComponent();
try
{
myPort = new System.IO.Ports.SerialPort("COM37");
myPort.BaudRate = 460800;
myPort.Parity = System.IO.Ports.Parity.None;
myPort.DataBits = 8;
myPort.StopBits = System.IO.Ports.StopBits.One;
myPort.Handshake = System.IO.Ports.Handshake.None;
myPort.ReadTimeout = 5000;
myPort.DataReceived += new SerialDataReceivedEventHandler(SerialPortDataReceived);
if (myPort.IsOpen == false) //if not open, open the port
myPort.Open();
}
catch (Exception ex)
{
Console.Write(ex.Message);
}
}
// Serial port received event handler
private void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
{
while (myPort.BytesToRead > 0)
{
var count = myPort.BytesToRead;
var bytes = new byte[count];
myPort.Read(bytes, 0, count);
AddResponseBytes(bytes);
}
}
List<byte> Data = new List<byte>();
private void AddResponseBytes(byte[] bytes)
{
// Analyzing message byte, wait for full message
ProcessResponse(responseData, msgType);
}
private void ProcessResponse(List<byte> bytes, mixCommandType responseType)
{
String str;
switch (responseType)
{
case CommandType.PC_TO_DEVICE_RESPONSE:
str = "Test text";
waitHandle_1.Set();
textBoxLog.Invoke((MethodInvoker)(() => textBoxLog.AppendText(str)));
break;
case CommandType.CHUNK_ACK:
waitHandle_2.Set();
break;
}
}
private void buttonSendFile_Click(object sender, EventArgs e)
{
// Open file and send message to device with file size and wait for ACK
waitHandle_1.WaitOne();
Console.WriteLine("Got response - Continue to send");
uint i = 0;
while (i < bytes.Length)
{
//Send chunk of the file to the device and whait for chunk ack
waitHandle_2.WaitOne();
}
}
}
那是因为 Invoke
将你传递给 UI (主)线程的任何东西分派(你可能已经知道),但是你的 UI 线程已经很忙了,它是在 buttonSendFile_Click
此处被屏蔽:
waitHandle_1.WaitOne();
或此处
waitHandle_2.WaitOne();
Invoke
也在阻塞调用 - 它不会 return 直到调度完成 - 所以你有经典的死锁。
一般来说,阻塞 UI 线程是不好的,尤其是在等待句柄上。要解决您的问题 - 使用后台线程执行您现在在 buttonSendFile_Click
处理程序中执行的操作。