diff --git a/DOAN.ServiceCore/DoanBackgroundService2.cs b/DOAN.ServiceCore/DoanBackgroundService2.cs index e2b3d72..b5b784e 100644 --- a/DOAN.ServiceCore/DoanBackgroundService2.cs +++ b/DOAN.ServiceCore/DoanBackgroundService2.cs @@ -27,20 +27,21 @@ namespace DOAN.ServiceCore notificationHubContext = hubContext; } - public Task StartAsync(CancellationToken cancellationToken) + public async Task StartAsync(CancellationToken cancellationToken) { - Console.WriteLine($"PCL定时任务开启!"); + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PCL定时任务开启!"); pLCTool = new PLCTool(); - pLCTool.ConnectPLC(); + bool isConnected = pLCTool.ConnectPLC(); + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC初始连接状态: {(isConnected ? "成功" : "失败")}"); // 初始化料架层和点位表数据 - RefreshData(null); + await RefreshData(null); // 启动后台任务 _executingTask = ExecuteAsync(_cancellationTokenSource.Token); // 设置定时器每10分钟刷新一次料架层和点位表 .FromMinutes(1) - refreshTimer = new Timer(RefreshData, null, TimeSpan.Zero, TimeSpan.FromMinutes(10)); + refreshTimer = new Timer(RefreshDataTimerCallback, null, TimeSpan.Zero, TimeSpan.FromMinutes(10)); - return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask; + await (_executingTask.IsCompleted ? _executingTask : Task.CompletedTask); } public async Task StopAsync(CancellationToken cancellationToken) @@ -78,21 +79,38 @@ namespace DOAN.ServiceCore /// private async Task ExecuteAsync(CancellationToken stoppingToken) { - Console.WriteLine($"PCL定时任务ExecuteAsync!"); + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PCL定时任务ExecuteAsync!"); try { while (!stoppingToken.IsCancellationRequested) { - // 读取PLC I/O状态(12个字节) + // 检查PLC连接状态,如果断开则尝试重连 + if (!pLCTool.IsConnected()) + { + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 检测到PLC连接断开,尝试重连..."); + // 由于PLCTool中的ReconnectPLC是无限重连的,这里不需要额外的延迟 + if (!pLCTool.ReconnectPLC()) + { + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC重连失败,跳过本次循环"); + continue; + } + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC重连成功"); + } + + // 读取PLC I/O状态(13个字节) byte[] plcSensorValues; try { // 读取1开头100,101。。。等全部地址数据 plcSensorValues = pLCTool.ReadAllValue("VB100", 13); + if (plcSensorValues != null) + { + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 成功读取PLC数据,字节数: {plcSensorValues.Length}"); + } } catch (Exception ex) { - Console.WriteLine($"读取PLC数据异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}"); + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 读取PLC数据异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}"); continue; } @@ -334,12 +352,12 @@ namespace DOAN.ServiceCore } catch (OperationCanceledException) { - Console.WriteLine("任务已取消"); + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 任务已取消"); } catch (Exception ex) { Console.WriteLine( - $"DoanBackGround线程ExecuteAsync异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}" + $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] DoanBackGround线程ExecuteAsync异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}" ); } } @@ -348,22 +366,53 @@ namespace DOAN.ServiceCore /// 刷新料架层和点位表数据 /// /// - private async void RefreshData(object state) + private async Task RefreshData(object state) { try { - Console.WriteLine($"刷新料架点位和按钮"); - storagelocationList = await Context.Queryable().ToListAsync(); - pointPositionList = await Context.Queryable().ToListAsync(); - buttonPositionList = await Context.Queryable().ToListAsync(); + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 开始刷新料架点位和按钮数据"); + // 检查PLC连接状态,如果断开则尝试重连 + if (!pLCTool.IsConnected()) + { + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 检测到PLC连接断开,尝试重连..."); + // 由于PLCTool中的ReconnectPLC是无限重连的,这里不需要额外的处理 + if (!pLCTool.ReconnectPLC()) + { + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC重连失败,但继续执行刷新操作"); + } + else + { + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC重连成功"); + } + } + + var newStoragelocationList = await Context.Queryable().ToListAsync(); + var newPointPositionList = await Context.Queryable().ToListAsync(); + var newButtonPositionList = await Context.Queryable().ToListAsync(); + + // 原子性地更新数据 + storagelocationList = newStoragelocationList; + pointPositionList = newPointPositionList; + buttonPositionList = newButtonPositionList; + + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 刷新料架点位和按钮完成: 料架{storagelocationList.Count}个, 点位{pointPositionList.Count}个, 按钮{buttonPositionList.Count}个"); } catch (Exception ex) { - Console.WriteLine($"刷新数据异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}"); + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 刷新数据异常: {ex.Message}\n堆栈跟踪: {ex.StackTrace}"); // 如果刷新失败,保持现有数据不变 } } + /// + /// 用于Timer回调的适配器方法 + /// + /// + private void RefreshDataTimerCallback(object state) + { + _ = RefreshData(state); // 不等待异步操作完成,避免阻塞Timer + } + /// /// 更新料架位置的箱子数量并记录库存日志 /// @@ -404,16 +453,24 @@ namespace DOAN.ServiceCore { try { + // 取消令牌源 _cancellationTokenSource.Cancel(); - _executingTask?.GetAwaiter().GetResult(); // 确保任务完成 + + // 等待任务完成,但设置超时以避免死锁 + if (_executingTask != null) + { + Task.WaitAny(_executingTask, Task.Delay(5000)); // 最多等待5秒 + } + + // 释放资源 _cancellationTokenSource.Dispose(); refreshTimer?.Change(Timeout.Infinite, Timeout.Infinite); refreshTimer?.Dispose(); - pLCTool.ConnectClose(); + pLCTool?.ConnectClose(); } catch (Exception ex) { - Console.WriteLine("DoanBackGround线程Dispose异常:" + ex.Message); + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] DoanBackGround线程Dispose异常: {ex.Message}"); } } } diff --git a/Infrastructure/PLC/PLCTool.cs b/Infrastructure/PLC/PLCTool.cs index 0e82fe7..6f4f63d 100644 --- a/Infrastructure/PLC/PLCTool.cs +++ b/Infrastructure/PLC/PLCTool.cs @@ -14,6 +14,8 @@ namespace DOAN.Infrastructure.PLC // 私有连接对象 private SiemensS7Net siemensTcpNet = null; private readonly string plcAddress; + private const int ReconnectDelayMs = 5000; + public PLCTool() { plcAddress = AppSettings.GetConfig("PLCConfig:Address"); @@ -34,21 +36,77 @@ namespace DOAN.Infrastructure.PLC var connect = siemensTcpNet.ConnectServer(); if (connect.IsSuccess) { - Console.WriteLine($"PLC连接成功,地址{plcAddress}"); + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC连接成功,地址{plcAddress}"); return true; } else { - Console.WriteLine($"PLC连接失败,地址{plcAddress}: {connect.Message}"); + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC连接失败,地址{plcAddress}: {connect.Message}"); return false; } } catch (Exception e) { - Console.WriteLine($"PLC连接失败,地址{plcAddress}: {e.Message}"); + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC连接失败,地址{plcAddress}: {e.Message}"); return false; } } + + /// + /// 检查PLC连接状态 + /// + /// 连接正常返回true,否则返回false + public bool IsConnected() + { + try + { + // 尝试读取一个字节来检查连接状态 + var result = siemensTcpNet.Read("VB100", 1); + if (!result.IsSuccess) + { + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC连接状态检查失败: {result.Message}"); + } + return result.IsSuccess; + } + catch (Exception ex) + { + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC连接状态检查异常: {ex.Message}"); + return false; + } + } + + /// + /// 重连PLC + /// + /// 重连成功返回true,失败返回false + public bool ReconnectPLC() + { + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 尝试重新连接PLC..."); + + // 先关闭现有连接 + ConnectClose(); + + // 重新创建PLC对象 + this.siemensTcpNet = new SiemensS7Net(SiemensPLCS.S200Smart, plcAddress) + { + ConnectTimeOut = 5000 + }; + + // 持续尝试重新连接,直到成功 + int attempt = 0; + while (true) + { + attempt++; + if (ConnectPLC()) + { + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC重连成功,尝试次数: {attempt}"); + return true; + } + + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC重连失败,尝试次数: {attempt}"); + System.Threading.Thread.Sleep(ReconnectDelayMs); + } + } /// /// 向PLC写入单个bit值 @@ -87,7 +145,7 @@ namespace DOAN.Infrastructure.PLC } else { - Console.WriteLine($"PLC IO 取值失败,PLC地址{plcAddress},访问地址为{addr},地址个数为{length}"); + Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] PLC IO 取值失败,PLC地址{plcAddress},访问地址为{addr},地址个数为{length}"); return null; } }