优化温度数据接收逻辑,修复端口号并增强线程安全性

This commit is contained in:
zqm
2025-10-31 10:08:11 +08:00
parent b48dae9c33
commit d9693c9f30
3 changed files with 233 additions and 74 deletions

View File

@@ -506,7 +506,23 @@ namespace JoyD.Windows.CS.Toprie
Console.WriteLine("Camera开始使用HTTP方式接收图像"); Console.WriteLine("Camera开始使用HTTP方式接收图像");
// 直接调用HTTP方式的图像接收 // 直接调用HTTP方式的图像接收
_deviceManager.StartImageReceiving(); _deviceManager.StartImageReceiving();
_deviceManager.StartTemperatureDataReceiving();
// 在调用StartTemperatureDataReceiving前先调用StopTemperatureDataReceiving保持与ResumeDetection方法一致的模式
// 并添加时间间隔检查,避免短时间内频繁调用
lock (_temperatureCallLock)
{
TimeSpan elapsed = DateTime.Now - _lastTemperatureStartCall;
if (elapsed.TotalMilliseconds < 500) // 至少间隔500毫秒
{
Console.WriteLine("避免短时间内重复启动温度数据接收,跳过本次调用");
}
else
{
_deviceManager.StopTemperatureDataReceiving();
_deviceManager.StartTemperatureDataReceiving();
_lastTemperatureStartCall = DateTime.Now;
}
}
_isReceivingImage = true; _isReceivingImage = true;
} }
} }
@@ -539,6 +555,9 @@ namespace JoyD.Windows.CS.Toprie
} }
private bool _isPaused = false; // 暂停状态标志 private bool _isPaused = false; // 暂停状态标志
// 记录上次启动温度数据接收的时间,用于防止短时间内重复调用
private DateTime _lastTemperatureStartCall = DateTime.MinValue;
private readonly object _temperatureCallLock = new object();
// Ping相关字段 // Ping相关字段
private System.Threading.Timer _pingTimer; private System.Threading.Timer _pingTimer;

View File

@@ -267,7 +267,7 @@ namespace JoyD.Windows.CS.Toprie
private NetworkStream _temperatureStream; private NetworkStream _temperatureStream;
private bool _isReceivingTemperatureData = false; private bool _isReceivingTemperatureData = false;
private ManualResetEvent _stopTemperatureEvent; private ManualResetEvent _stopTemperatureEvent;
private const int TEMPERATURE_TCP_PORT = 7682; // 温度数据TCP端口 private const int TEMPERATURE_TCP_PORT = 8081; // 温度数据TCP端口 - 修正为热像仪SDK文档中指定的端口
/// <summary> /// <summary>
/// 项目路径属性 /// 项目路径属性
@@ -888,6 +888,13 @@ namespace JoyD.Windows.CS.Toprie
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartTemperatureDataReceiving() - 连接未建立,无法开始温度数据接收"); Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartTemperatureDataReceiving() - 连接未建立,无法开始温度数据接收");
return; return;
} }
// 添加额外的状态检查,避免不必要的重复启动
if (_isReceivingTemperatureData && _temperatureReceiveThread != null && _temperatureReceiveThread.IsAlive)
{
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartTemperatureDataReceiving() - 温度数据接收已经在进行中,避免重复启动");
return;
}
} }
Log("开始使用TCP方式接收温度数据"); Log("开始使用TCP方式接收温度数据");
@@ -896,25 +903,37 @@ namespace JoyD.Windows.CS.Toprie
// 确保之前的连接已关闭 // 确保之前的连接已关闭
StopTemperatureDataReceiving(); StopTemperatureDataReceiving();
// 重置停止事件 // 在锁内执行所有关键状态更新,确保原子性
_stopTemperatureEvent = new ManualResetEvent(false);
lock (_lockObject) lock (_lockObject)
{ {
// 重置停止事件
_stopTemperatureEvent?.Dispose();
_stopTemperatureEvent = new ManualResetEvent(false);
_isReceivingTemperatureData = true; _isReceivingTemperatureData = true;
} }
// 创建并启动温度数据接收线程 // 创建并启动温度数据接收线程
_temperatureReceiveThread = new Thread(ReceiveTemperatureDataWithTcp) Thread newThread = new Thread(ReceiveTemperatureDataWithTcp)
{ {
IsBackground = true, IsBackground = true,
Name = "TemperatureReceiveThread" Name = "TemperatureReceiveThread"
}; };
_temperatureReceiveThread.Start();
// 在锁内更新线程引用
lock (_lockObject)
{
_temperatureReceiveThread = newThread;
}
// 启动线程
newThread.Start();
} }
catch (Exception ex) catch (Exception ex)
{ {
// 记录异常 // 记录异常
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartTemperatureDataReceiving() - 异常: {ex.Message}"); Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StartTemperatureDataReceiving() - 异常: {ex.Message}");
// 在异常情况下确保状态正确重置
lock (_lockObject) lock (_lockObject)
{ {
_isReceivingTemperatureData = false; _isReceivingTemperatureData = false;
@@ -943,26 +962,36 @@ namespace JoyD.Windows.CS.Toprie
return; return;
} }
// 使用变量保存需要释放的资源,减少锁的持有时间
ManualResetEvent stopEventToDispose = null;
Thread threadToJoin = null;
NetworkStream streamToClose = null;
TcpClient clientToClose = null;
try try
{ {
// 线程安全地更新状态 // 在一个锁内收集所有需要清理的资源和更新状态
lock (_lockObject) lock (_lockObject)
{ {
_isReceivingTemperatureData = false; _isReceivingTemperatureData = false;
stopEventToDispose = _stopTemperatureEvent;
threadToJoin = _temperatureReceiveThread;
streamToClose = _temperatureStream;
clientToClose = _temperatureTcpClient;
// 立即重置引用,避免资源被重新使用
_temperatureStream = null;
_temperatureTcpClient = null;
_temperatureReceiveThread = null;
_stopTemperatureEvent = null;
} }
// 通知线程停止 // 在锁外通知线程停止,避免死锁
ManualResetEvent stopEvent = null; if (stopEventToDispose != null)
lock (_lockObject)
{
stopEvent = _stopTemperatureEvent;
}
if (stopEvent != null)
{ {
try try
{ {
stopEvent.Set(); stopEventToDispose.Set();
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
{ {
@@ -970,54 +999,75 @@ namespace JoyD.Windows.CS.Toprie
} }
} }
// 等待线程结束 // 在锁外等待线程结束,避免死锁
Thread temperatureThread = null; if (threadToJoin != null && threadToJoin.IsAlive)
lock (_lockObject)
{ {
temperatureThread = _temperatureReceiveThread; Log("等待温度接收线程结束...");
// 增加等待时间,确保线程有足够时间完成清理
if (threadToJoin.Join(2000)) // 等待最多2秒
{
Log("温度接收线程已正常停止");
}
else
{
Log("警告:温度接收线程可能未正常停止,已超时");
}
} }
if (temperatureThread != null && temperatureThread.IsAlive) // 在锁外关闭TCP客户端和流避免死锁
{ if (streamToClose != null)
temperatureThread.Join(1000); // 等待最多1秒
Log("温度接收线程已停止");
}
// 关闭TCP客户端和流
NetworkStream stream = null;
TcpClient client = null;
lock (_lockObject)
{
stream = _temperatureStream;
client = _temperatureTcpClient;
_temperatureStream = null;
_temperatureTcpClient = null;
}
if (stream != null)
{ {
try try
{ {
stream.Close(); streamToClose.Close();
}
catch (Exception ex)
{
Log($"关闭NetworkStream时出现异常: {ex.Message}");
} }
catch (Exception)
{}
} }
if (client != null) if (clientToClose != null)
{ {
try try
{ {
client.Close(); clientToClose.Close();
}
catch (Exception ex)
{
Log($"关闭TcpClient时出现异常: {ex.Message}");
}
}
// 释放停止事件
if (stopEventToDispose != null)
{
try
{
stopEventToDispose.Dispose();
}
catch (Exception ex)
{
Log($"释放ManualResetEvent时出现异常: {ex.Message}");
} }
catch (Exception)
{}
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopTemperatureDataReceiving() - 异常: {ex.Message}"); Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] StopTemperatureDataReceiving() - 异常: {ex.Message}");
// 在异常情况下,再次尝试重置状态
try
{
lock (_lockObject)
{
_isReceivingTemperatureData = false;
_temperatureStream = null;
_temperatureTcpClient = null;
_temperatureReceiveThread = null;
}
}
catch {}
} }
finally finally
{ {
@@ -1032,63 +1082,131 @@ namespace JoyD.Windows.CS.Toprie
{ {
Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReceiveTemperatureDataWithTcp() - 开始执行"); Log($"[{DateTime.Now:HH:mm:ss.fff}] [线程:{Thread.CurrentThread.ManagedThreadId}] ReceiveTemperatureDataWithTcp() - 开始执行");
// 使用局部变量存储资源避免在finally中访问可能已被释放的字段
TcpClient localTcpClient = null;
NetworkStream localStream = null;
List<byte> temperatureDataAccumulator = new List<byte>();
byte[] buffer = new byte[8192]; // 温度数据缓冲区
try try
{ {
// 创建TCP客户端并连接到设备的温度数据端口 // 创建TCP客户端并连接到设备的温度数据端口
_temperatureTcpClient = new TcpClient localTcpClient = new TcpClient
{ {
ReceiveTimeout = 5000, ReceiveTimeout = 5000,
SendTimeout = 5000 SendTimeout = 5000
}; };
IAsyncResult result = _temperatureTcpClient.BeginConnect(_deviceIp, TEMPERATURE_TCP_PORT, null, null); Log($"正在连接到温度数据端口 {TEMPERATURE_TCP_PORT}...");
IAsyncResult result = localTcpClient.BeginConnect(_deviceIp, TEMPERATURE_TCP_PORT, null, null);
bool connected = result.AsyncWaitHandle.WaitOne(3000, true); bool connected = result.AsyncWaitHandle.WaitOne(3000, true);
if (!connected || !_temperatureTcpClient.Connected) if (!connected || !localTcpClient.Connected)
{ {
Log("温度数据TCP连接失败超时"); Log("温度数据TCP连接失败超时");
localTcpClient.Close();
return; return;
} }
_temperatureTcpClient.EndConnect(result); localTcpClient.EndConnect(result);
Log("温度数据TCP连接成功"); Log("温度数据TCP连接成功");
// 获取网络流 // 获取网络流
_temperatureStream = _temperatureTcpClient.GetStream(); localStream = localTcpClient.GetStream();
_temperatureStream.ReadTimeout = 5000; localStream.ReadTimeout = 5000;
byte[] buffer = new byte[8192]; // 温度数据缓冲区 // 更新类成员变量,在锁内进行
List<byte> temperatureDataAccumulator = new List<byte>(); lock (_lockObject)
{
_temperatureTcpClient = localTcpClient;
_temperatureStream = localStream;
}
// 发送开始温度数据传输的命令 // 发送开始温度数据传输的命令
byte[] startCommand = Encoding.ASCII.GetBytes("start_temp_transfer\r\n"); byte[] startCommand = Encoding.ASCII.GetBytes("start_temp_transfer\r\n");
_temperatureStream.Write(startCommand, 0, startCommand.Length); localStream.Write(startCommand, 0, startCommand.Length);
_temperatureStream.Flush(); localStream.Flush();
Log("已发送开始温度数据传输命令,开始接收温度数据"); Log("已发送开始温度数据传输命令,开始接收温度数据");
while (!_stopTemperatureEvent.WaitOne(0)) // 使用局部变量引用停止事件,避免在循环中重复加锁
ManualResetEvent currentStopEvent = null;
lock (_lockObject)
{ {
currentStopEvent = _stopTemperatureEvent;
}
// 循环读取数据,使用更安全的退出机制
bool shouldContinue = true;
while (shouldContinue)
{
// 检查停止信号
if (currentStopEvent != null && currentStopEvent.WaitOne(0))
{
Log("接收到停止信号,准备退出温度数据接收循环");
shouldContinue = false;
break;
}
// 检查是否仍在接收状态
lock (_lockObject)
{
if (!_isReceivingTemperatureData)
{
Log("接收状态已更改,准备退出温度数据接收循环");
shouldContinue = false;
break;
}
}
try try
{ {
// 检查连接状态 // 检查连接状态
if (!_temperatureTcpClient.Connected) if (localTcpClient == null || !localTcpClient.Connected)
{ {
Log("温度数据TCP连接已断开"); Log("温度数据TCP连接已断开");
shouldContinue = false;
break; break;
} }
// 读取数据 // 检查流是否可
int bytesRead = _temperatureStream.Read(buffer, 0, buffer.Length); if (localStream == null || !localStream.CanRead)
if (bytesRead > 0)
{ {
// 将读取的数据添加到累积器 Log("网络流不可读,停止接收");
byte[] receivedBytes = new byte[bytesRead]; shouldContinue = false;
Array.Copy(buffer, receivedBytes, bytesRead); break;
temperatureDataAccumulator.AddRange(receivedBytes); }
// 处理累积的数据 // 检查是否有数据可读,避免阻塞
ProcessReceivedTemperatureData(temperatureDataAccumulator); if (localStream.DataAvailable)
{
// 读取数据
int bytesRead = localStream.Read(buffer, 0, buffer.Length);
if (bytesRead > 0)
{
// 将读取的数据添加到累积器
byte[] receivedBytes = new byte[bytesRead];
Array.Copy(buffer, receivedBytes, bytesRead);
// 线程安全地更新累积器或直接处理
lock (temperatureDataAccumulator)
{
temperatureDataAccumulator.AddRange(receivedBytes);
ProcessReceivedTemperatureData(temperatureDataAccumulator);
}
}
else
{
// 读取到0字节表示连接已关闭
Log("远程主机关闭了连接");
shouldContinue = false;
break;
}
}
else
{
// 如果没有数据可读短暂休眠避免CPU占用过高
Thread.Sleep(10);
} }
} }
catch (TimeoutException) catch (TimeoutException)
@@ -1099,12 +1217,14 @@ namespace JoyD.Windows.CS.Toprie
catch (IOException ex) catch (IOException ex)
{ {
Log($"温度数据接收IO异常: {ex.Message}"); Log($"温度数据接收IO异常: {ex.Message}");
shouldContinue = false;
break; break;
} }
catch (Exception ex) catch (Exception ex)
{ {
Log($"温度数据接收异常: {ex.Message}"); Log($"温度数据接收异常: {ex.Message}");
// 非致命异常,继续尝试 // 非致命异常,短暂休眠后继续尝试
Thread.Sleep(100);
} }
} }
} }
@@ -1120,10 +1240,28 @@ namespace JoyD.Windows.CS.Toprie
lock (_lockObject) lock (_lockObject)
{ {
_isReceivingTemperatureData = false; _isReceivingTemperatureData = false;
// 不在这里调用StopTemperatureDataReceiving避免递归调用和潜在死锁
// 由调用者负责调用StopTemperatureDataReceiving
} }
// 清理资源 // 清理局部资源
StopTemperatureDataReceiving(); try
{
if (localStream != null)
{
localStream.Close();
}
}
catch {}
try
{
if (localTcpClient != null)
{
localTcpClient.Close();
}
}
catch {}
} }
} }

View File

@@ -8,10 +8,12 @@
1. 暂停或恢复时设置暂停状态调用更新Info 1. 暂停或恢复时设置暂停状态调用更新Info
2. 断开或连接时设置连接状态调用更新Info 2. 断开或连接时设置连接状态调用更新Info
3. Ping通状态变化时修改Ping状态调用更新Info 3. Ping通状态变化时修改Ping状态调用更新Info
4. 图像更新时, 保存LastImage, 调用更新到UI 4. 温度数据更新时调用更新Info
5. 图像更新时, 保存LastImage, 调用更新到UI
### 更新Info: ### 更新Info:
1. 如果暂停显示暂停信息否则如果Ping不通或断开显示重连信息否则清空InfoImage 1. 如果暂停显示暂停信息否则如果Ping不通或断开显示重连信息
2. 最后调用更新UI 2. 如果有温度数据显示最高温度否则清空Info
3. 最后调用更新UI
### 更新UI: ### 更新UI:
1. 先将LastImage绘制到全局缓冲 1. 先将LastImage绘制到全局缓冲
2. 再将InfoImage绘制到缓冲 2. 再将InfoImage绘制到缓冲