Imports System.Net.Sockets Imports System.Text Imports System.Threading Imports System.Windows.Forms Imports UTS_Core.UTSModule.DbConnect Imports UTS_Core.UTSModule.DbTableModel.Customer Imports UTS_Core.UTSModule.Login Imports UTS_Core.UTSModule.Production Imports UTS_Core.UTSModule.Service Imports UTS_Core.UTSModule.Station Imports UTS_Core.UTSModule.Test Imports UTS_Core.UTSModule.Test.StatusMonitor Namespace UTSModule ''' '''UTS系列App与UtsCore交互为避免重复书写产生的中间类 ''' Public Class UtsAppForm Implements IDisposable ''' 本地服务索引 Private _serviceIndex As Integer ''' 本地服务角色,0客户端,1服务器 Private _serviceRoles As Integer ''' 本地服务别名 Private _serviceAlias As String ''' 本地服务组名(子网名) Private _serviceGroup As String ''' 软件名称 Private _appName As String ''' 软件版本 Private _appVersion As String ''' 测试记录表名 Private _testLogTableName As String ''' 登录用户信息 Private _userInfo As UserInfo ''' 鉴权文件信息 Private _license As License.License ''' 当前选择站位信息 Private _processStation As ProcessStation ''' 测试结果内容 Private ReadOnly _testResult As TestResult ''' 软件注册器 Private _register As AppRegister ''' 服务通讯TCP客户端 Private _tcpClient As TcpClient ''' 是否释放当前类 Private _disposedValue As Boolean ''' 站位观察者集合 Private ReadOnly _observerList As List(Of IProcessStation) ''' 当前软件所属站位类型,None则无类型 Private _stationType As ProcessStation.StationTypeEnum ''' UTS软件单例对象 Private Shared _utsApp As UtsAppForm ''' UTS软件单例对象初始化锁 Private Shared InitLock As New Object 'CZH 2023-05-19 增加站位列表集 Public projectInfo As Project.ProjectInfo ''' ''' 创建单例对象 ''' ''' Public Shared Function CreateSingleton() As UtsAppForm If _utsApp IsNot Nothing Then Return _utsApp End If SyncLock InitLock Thread.MemoryBarrier() If _utsApp Is Nothing Then _utsApp = New UtsAppForm End If End SyncLock Return _utsApp End Function Private Sub New() _disposedValue = False _testLogTableName = String.Empty _testResult = New TestResult() _observerList = New List(Of IProcessStation)() ShowUnSearchOrderIDTip = True SaveUnSearchOrderIDLog = False IsInitialized = False End Sub ''' ''' 添加站位修改观察者,唯一添加 ''' ''' Public Sub AddStatisticsObserver(observer As IProcessStation) If _observerList.Contains(observer) = False Then _observerList.Add(observer) End Sub ''' ''' 移除站位修改观察者 ''' ''' Public Sub RemoveStatisticsObserver(observer As IProcessStation) If _observerList.Contains(observer) Then _observerList.Remove(observer) End Sub ''' ''' 清空站位修改观察者 ''' Public Sub ClearStatisticsObserver() _observerList.Clear() End Sub ''' ''' 初始化完成 ''' ''' Public Property IsInitialized As Boolean ''' ''' 用户账号信息 ''' ''' Public ReadOnly Property Account As UserInfo Get Return _userInfo End Get End Property ''' ''' 服务索引 ''' ''' Public ReadOnly Property ServiceIndex As Integer Get Return _serviceIndex End Get End Property ''' ''' 服务角色,0客户端,1服务器 ''' ''' Public ReadOnly Property ServiceRoles As Integer Get Return _serviceRoles End Get End Property ''' ''' 服务别名 ''' ''' Public ReadOnly Property ServiceAlias As String Get Return _serviceAlias End Get End Property ''' ''' 服务所属分组 ''' ''' Public ReadOnly Property ServiceGroup As String Get Return _serviceGroup End Get End Property ''' ''' 鉴权文件信息 ''' ''' Public ReadOnly Property License As License.License Get Return _license End Get End Property ''' ''' 工厂产线信息 ''' ''' Public ReadOnly Property ProductionLines() As ProductionLinesManager Get Return ProductionLinesManager.CreateManager() End Get End Property ''' ''' 测试站信息 ''' ''' Public ReadOnly Property TestResult() As TestResult Get Return _testResult End Get End Property ''' ''' 测试站信息 ''' ''' Public ReadOnly Property ProcessStation() As ProcessStation Get Return _processStation End Get End Property ''' ''' APP注册机 ''' ''' Public ReadOnly Property Register() As AppRegister Get Return _register End Get End Property ''' ''' 测试记录表名 ''' ''' Public ReadOnly Property TestLogTableName() As String Get Return _testLogTableName End Get End Property ''' ''' 未查询到数据库订单号时是否显示提示 ''' ''' Public Property ShowUnSearchOrderIDTip() As Boolean ''' ''' 允许保存未查询到订单号的记录入库,显示提示时,此字段不生效。 ''' ''' Public Property SaveUnSearchOrderIDLog() As Boolean Private _testChangedTime As DateTime ''' ''' 初始化信息,唯一初始化 ''' ''' 软件选择站位时,可以选择的站位类型,默认显示测试站位 Public Sub Initialize(Optional sType As ProcessStation.StationTypeEnum = ProcessStation.StationTypeEnum.Test) If IsInitialized = True Then Return IsInitialized = True '建立Tcp与服务通讯的套接字 ConnectDataService() '显示登录窗体 ShowLoginForm() '注册软件 RegisterApp() '给测试记录附上初始值 InitializeTestResult() '记录站位类型提供过滤筛选 _stationType = sType '记录测试变化信息 _testChangedTime = Now AddHandler TestStatusMonitor.TestStatusChanged, AddressOf TestStatusChanged End Sub ''' ''' 测试状态改变处理事件 ''' ''' ''' Private Sub TestStatusChanged(sender As Object, e As TestStatusChangedEventArgs) '记录上一次变化的时间 _testChangedTime = Now End Sub #Region "服务通讯模块" ''' ''' 连接数据服务,异步执行 ''' Public Sub ConnectDataService() ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf KeepTcpClientAlive)) End Sub ''' ''' 保持与数据服务的Tcp连接存活 ''' ''' Private Sub KeepTcpClientAlive(state As Object) Static remoteIp As String = "127.0.0.1" While True Try If _tcpClient Is Nothing OrElse _tcpClient.Client Is Nothing Then _tcpClient = New TcpClient(remoteIp, CInt(UtsRegistry.DataServicePort)) '开启监听 ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf ClientReceive), _tcpClient) '通知服务软件启动 SendAppMsg(_tcpClient, TaskJsonParam.AppMsgTypes.AppStart) '开启与服务通知心跳包 ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SendAppAlive), _tcpClient) End If Catch ex As Exception Console.WriteLine($"InitTcpClient Error:{ex.Message}!") End Try Thread.Sleep(1000 * 10) End While End Sub ''' ''' 定期发送App心跳包 ''' ''' Private Sub SendAppAlive(state As Object) Dim client As TcpClient = CType(state, TcpClient) While True If client Is Nothing OrElse client.Client Is Nothing Then Return Thread.Sleep(1000) SendAppMsg(client, TaskJsonParam.AppMsgTypes.AppAlive) End While End Sub ''' ''' 主动向服务发送变化数据 ''' ''' 变化类型 ''' 字段内容 Public Sub SendAppMsg(type As TaskJsonParam.AppMsgTypes, fileds As Dictionary(Of String, String)) If _tcpClient Is Nothing OrElse _tcpClient.Client Is Nothing Then Return Dim param As New TaskJsonParam param.CmdName = TaskJsonParam.CmdNamesEnum.AppMsg param.AppName = Application.ProductName If _userInfo Is Nothing Then param.User = "Admin" Else param.User = _userInfo.UserName End If param.AppInfo.Add("AppStatus", type.ToString) For Each filed As KeyValuePair(Of String, String) In fileds param.AppInfo.Add(filed.Key, filed.Value) Next SendJsonString(_tcpClient, TaskJsonParam.SerializeToJson(param) & vbCrLf) End Sub ''' ''' 发送App上报信息 ''' ''' ''' Private Sub SendAppMsg(client As TcpClient, type As TaskJsonParam.AppMsgTypes) If client Is Nothing OrElse client.Client Is Nothing Then Return Dim param As New TaskJsonParam param.CmdName = TaskJsonParam.CmdNamesEnum.AppMsg param.AppName = Application.ProductName If _userInfo Is Nothing Then param.User = "Admin" Else param.User = _userInfo.UserName End If Select Case type Case TaskJsonParam.AppMsgTypes.AppStart param.AppInfo.Add("AppStatus", TaskJsonParam.AppMsgTypes.AppStart.ToString) param.AppInfo.Add("Version", Application.ProductVersion) param.AppInfo.Add("AppPath", Application.StartupPath) Case TaskJsonParam.AppMsgTypes.AppClose param.AppInfo.Add("AppStatus", TaskJsonParam.AppMsgTypes.AppClose.ToString) Case TaskJsonParam.AppMsgTypes.AppAlive param.AppInfo.Add("AppStatus", TaskJsonParam.AppMsgTypes.AppAlive.ToString) param.AppInfo.Add("Version", Application.ProductVersion) If _processStation Is Nothing Then param.AppInfo.Add("Project", "") param.AppInfo.Add("Station", "") param.AppInfo.Add("TestPlan", "") Else param.AppInfo.Add("Project", _processStation.ParentProject.Name) param.AppInfo.Add("Station", _processStation.Name) param.AppInfo.Add("TestPlan", _processStation.Packet.FileName) End If param.AppInfo.Add("UtsStatus", StatusMonitor.ComportStatusMonitor.ComportStatus.ToString) param.AppInfo.Add("TestStatus", StatusMonitor.TestStatusMonitor.TestStatus.ToString) Case TaskJsonParam.AppMsgTypes.StationChanged param.AppInfo.Add("AppStatus", TaskJsonParam.AppMsgTypes.StationChanged.ToString) param.AppInfo.Add("Project", _processStation.ParentProject.Name) param.AppInfo.Add("Station", _processStation.Name) param.AppInfo.Add("TestPlan", _processStation.Packet.FileName) End Select SendJsonString(client, TaskJsonParam.SerializeToJson(param) & vbCrLf) End Sub ''' ''' 发送数据 ''' ''' ''' Private Sub SendJsonString(tcpClient As TcpClient, jsonString As String) Static SendLock As New Object Static FailCount As Integer = 0 If tcpClient Is Nothing OrElse tcpClient.Client Is Nothing Then Return SyncLock SendLock Try Dim buf() As Byte = Encoding.UTF8.GetBytes(jsonString) tcpClient.GetStream().Write(buf, 0, buf.Length) FailCount = 0 Catch ex As Exception FailCount += 1 If FailCount > 3 Then FailCount = 0 tcpClient.Close() End If Console.WriteLine($"TcpClient Send Data Error!{ex.Message}") End Try End SyncLock End Sub ''' ''' APP接收服务器数据 ''' ''' Public Sub ClientReceive(state As Object) Dim client As TcpClient = CType(state, TcpClient) Dim len As Integer Dim buf(4096) As Byte While True Try len = client.Client.Receive(buf) Catch ex As Exception Console.WriteLine($"TcpServer Receive Error:{ex.Message},Recycling Connection!") Exit While End Try If len = 0 Then Console.WriteLine($"TcpServer {client.Client.RemoteEndPoint} Is Disconnect.") Exit While End If Dim destBuf(len - 1) As Byte Array.Copy(buf, 0, destBuf, 0, destBuf.Length) ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf DealData), New TcpData(client, destBuf)) End While End Sub Public Sub DealData(state As Object) Dim data As TcpData = CType(state, TcpData) Dim jsonStrings As String = Encoding.UTF8.GetString(data.Buffer) '校验Json字符串,必须以回车换行结尾 If jsonStrings.EndsWith(vbCrLf) = False Then Return For Each jsonString As String In jsonStrings.Split(New String() {vbCrLf}, Integer.MaxValue, StringSplitOptions.RemoveEmptyEntries) Try '反序列化 Dim taskParam As TaskJsonParam = TaskJsonParam.DeserializeFormJson(jsonString) '反序列化失败,则处理下一包 If IsNothing(taskParam) Then Continue For '处理数据,返回回复数据 Dim reply As TaskJsonParam = DealReceiveData(taskParam) '无需回复,则处理下一包 If reply Is Nothing Then Continue For '序列化回复数据 Dim replyString As String = TaskJsonParam.SerializeToJson(reply) & vbCrLf '回复数据 SendJsonString(data.Client, replyString) Catch ex As Exception Console.WriteLine($"Deal Receive Data Error!{ex.Message}") End Try Next End Sub ''' ''' 处理数据,并返回回复数据 ''' ''' 需要处理的数据参数 ''' 需要回复的数据 Private Function DealReceiveData(taskParam As TaskJsonParam) As TaskJsonParam Dim replyParam As TaskJsonParam Select Case taskParam.CmdName Case TaskJsonParam.CmdNamesEnum.AppMsg replyParam = Nothing '不处理 If taskParam.CmdStatus = "Fail" Then Exit Select Dim status As TaskJsonParam.AppMsgTypes Try status = CType([Enum].Parse(GetType(TaskJsonParam.AppMsgTypes), taskParam.AppInfo("AppStatus")), TaskJsonParam.AppMsgTypes) Select Case status Case TaskJsonParam.AppMsgTypes.AppAlive Static dbLastStatus As Integer = -1 Static ftpLastStatus As Integer = -1 Static cacheLastStatus As Integer = -1 Static syncLastStatus As String = "" Static servicesLastStatus As String = "" Dim dbStatus As Integer = -1 Dim ftpStatus As Integer = -1 Dim cacheCount As Integer = -1 Integer.TryParse(taskParam.AppInfo("DbStatus"), dbStatus) If dbLastStatus <> dbStatus Then dbLastStatus = dbStatus RaiseEvent DbStatusChanged(dbLastStatus) End If Integer.TryParse(taskParam.AppInfo("FtpStatus"), ftpStatus) If ftpLastStatus <> ftpStatus Then ftpLastStatus = ftpStatus RaiseEvent FtpStatusChanged(dbLastStatus) End If Dim flg As Boolean = False '是否更新同步状态文本 Integer.TryParse(taskParam.AppInfo("CacheCount"), cacheCount) If cacheLastStatus <> cacheCount Then cacheLastStatus = cacheCount flg = True End If Dim syncTime As String = taskParam.AppInfo("SyncTime") If syncLastStatus <> syncTime Then syncLastStatus = syncTime flg = True End If If flg Then RaiseEvent SyncTimeChanged($"[{cacheLastStatus}] {syncLastStatus}") End If Dim tmpTime As Date If Date.TryParse(syncTime, tmpTime) Then If (Now - tmpTime).TotalMinutes > 6 Then DatabaseStatusMonitor.DatabaseSyncStatus = DatabaseStatusMonitor.DatabaseSyncStatusEnum.UnCompleted Else DatabaseStatusMonitor.DatabaseSyncStatus = DatabaseStatusMonitor.DatabaseSyncStatusEnum.Completed End If Else DatabaseStatusMonitor.DatabaseSyncStatus = DatabaseStatusMonitor.DatabaseSyncStatusEnum.Unknown End If Dim services As String = taskParam.AppInfo("Services") If servicesLastStatus <> services Then servicesLastStatus = services RaiseEvent ServicesChanged(servicesLastStatus) End If Case Else Console.WriteLine($"status:{status}") End Select Catch ex As Exception End Try Case TaskJsonParam.CmdNamesEnum.ServiceMsg replyParam = DealServiceMsg(taskParam) Case Else replyParam = Nothing Console.WriteLine($"Invalid CmdName:{taskParam.CmdName}") End Select Return replyParam End Function Public Event DbStatusChanged(status As Integer) Public Event FtpStatusChanged(status As Integer) Public Event SyncTimeChanged(timeString As String) Public Event ServicesChanged(services As String) Private Function DealServiceMsg(taskParam As TaskJsonParam) As TaskJsonParam Dim replyParam As New TaskJsonParam(taskParam.CmdName, True) Try If taskParam.AppInfo Is Nothing OrElse taskParam.AppInfo.Count = 0 Then Return Nothing '依此判断App修改类型 Dim status As TaskJsonParam.ServiceMsgTypes status = CType([Enum].Parse(GetType(TaskJsonParam.ServiceMsgTypes), taskParam.AppInfo("AppStatus")), TaskJsonParam.ServiceMsgTypes) Select Case status Case TaskJsonParam.ServiceMsgTypes.DbHostChange UtsDb.DbHostChanged(taskParam.AppInfo("DbHost")) Case TaskJsonParam.ServiceMsgTypes.FtpHostChange UtsFtp.CreateObject.FtpHost = taskParam.AppInfo("FtpHost") End Select replyParam.CmdStatus = TaskJsonParam.CmdStatusEnum.Pass.ToString() Catch ex As Exception replyParam.CmdStatus = TaskJsonParam.CmdStatusEnum.Fail.ToString() replyParam.CmdMsg = ex.Message End Try Return replyParam End Function Public Class TcpData Sub New(c As TcpClient, buf() As Byte) Client = c Buffer = buf End Sub Public Client As TcpClient Public Buffer() As Byte End Class #End Region ''' ''' 显示登陆页面 ''' Private Sub ShowLoginForm() If LoginParams.IsShowLoginForm = False Then Return Using frmLogin As New LoginForm If frmLogin.ShowDialog() = DialogResult.OK Then _userInfo = frmLogin.UserLoginInfo _license = frmLogin.UserLicense Else Throw New Exception($"User Quit!") End If End Using End Sub ''' ''' 注册APP ''' Private Sub RegisterApp() _serviceIndex = CInt(UtsRegistry.DataServiceIndex) _serviceRoles = CInt(UtsRegistry.Roles) _serviceAlias = UtsRegistry.DataServiceAlias _serviceGroup = UtsRegistry.BarnchNet _appName = Application.ProductName _appVersion = Application.ProductVersion AppRegister.CreateAppListTable() '创建日志列表 AppRegister.CreateAppLogTable() '创建日志记录表 _register = New AppRegister(_serviceIndex, _appName, _appVersion) ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf UpdateAppAliveTime)) ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SaveServiceLog)) End Sub ''' ''' 初始化测试结果字段信息 ''' Private Sub InitializeTestResult() _testResult.UserID = Account.UserId _testResult.ServiceID = _serviceIndex _testResult.AppName = _appName & _appVersion End Sub ''' ''' 填充测试结果订单信息字段 ''' Private Sub FillTestResultOrderID() _testResult.OrderID = DbConnector.SearchOrderID(_processStation.ParentProject.Index, _testResult.DUT_SN) If _testResult.OrderID = -1 Then '仅自动生成条码模式下保证有订单号,其余模式条码不检测 If _processStation.SnType <> ProcessStation.SnTypeEnum.Auto Then Return If ShowUnSearchOrderIDTip Then If MsgBox($"未查询到条码[{_testResult.DUT_SN}]对应的订单号,是否继续录入?", MsgBoxStyle.OkCancel, "Tip") <> MsgBoxResult.Ok Then Throw New Exception($"未查询条码[{_testResult.DUT_SN}]到指定订单号") End If Else If SaveUnSearchOrderIDLog = False Then Throw New Exception($"未查询条码[{_testResult.DUT_SN}]到指定订单号") End If End If End If End Sub ''' ''' 根据序号分配给测试记录的订单号,并将测试记录写入到数据库中,提交成功后会重置测试记录。 ''' Public Sub CommitTestResult(Optional uniqueRecord As Boolean = False) '填充订单号 FillTestResultOrderID() '提交测试记录 If uniqueRecord Then DbConnector.SaveUniqueTestLog(_testLogTableName, _processStation, _testResult) Else DbConnector.SaveTestLog(_testLogTableName, _processStation, _testResult) End If End Sub ''' ''' 根据项目名与站位名称,加载项目站信息 ''' ''' ''' Public Sub LoadStation(projectName As String, stationName As String, Optional filed As Dictionary(Of String, String) = Nothing) If String.IsNullOrWhiteSpace(projectName) Or String.IsNullOrWhiteSpace(stationName) Then Return End If Using dlg As New DlgLoadStation dlg.UserInfo = Account '用与初始化项目信息时,更新操作者,同时可用与根据用户权限筛选项目 If dlg.InitProjectStationWithoutShow(projectName, stationName) Then _processStation = dlg.ProcessStation projectInfo = dlg._projectInfo If String.IsNullOrEmpty(ProcessStation.Packet.FileName) Then ProcessStation.Packet.CreatePacket() '新建测试站包 Else ProcessStation.Packet.LoadPacket() '读取测试站包 End If StationChanged(ProcessStation, filed) '测试站修改 End If End Using End Sub ''' ''' 显示切换项目站信息页面,供用户修改测试站 ''' Public Sub ChangeStation(Optional filed As Dictionary(Of String, String) = Nothing) Using dlg As New DlgLoadStation dlg.UserInfo = Account '用与初始化项目信息时,更新操作者,同时可用与根据用户权限筛选项目 If _processStation Is Nothing Then If dlg.ShowDialog("", "", _stationType) <> DialogResult.OK Then Return Else If dlg.ShowDialog(_processStation.ParentProject.Name, _processStation.Name, _stationType) <> DialogResult.OK Then Return End If If dlg.ProcessStation Is Nothing Then Return _processStation = dlg.ProcessStation projectInfo = dlg._projectInfo If String.IsNullOrEmpty(ProcessStation.Packet.FileName) Then ProcessStation.Packet.CreatePacket() '新建测试站包 Else ProcessStation.Packet.LoadPacket() '读取测试站包 End If StationChanged(ProcessStation, filed) End Using End Sub '2022-10-19 CZH 该改动针对已固定某一站的 资源包刷新 Public Sub ChangeStation(processStation As ProcessStation, Optional filed As Dictionary(Of String, String) = Nothing) 'Using dlg As New DlgLoadStation ' dlg.UserInfo = Account '用与初始化项目信息时,更新操作者,同时可用与根据用户权限筛选项目 ' If _processStation Is Nothing Then ' If dlg.ShowDialog("", "", _stationType) <> DialogResult.OK Then Return ' Else ' If dlg.ShowDialog(_processStation.ParentProject.Name, _processStation.Name, _stationType) <> DialogResult.OK Then Return ' End If If processStation Is Nothing Then Return _processStation = processStation If String.IsNullOrEmpty(processStation.Packet.FileName) Then processStation.Packet.CreatePacket() '新建测试站包 Else processStation.Packet.LoadPacket() '读取测试站包 End If StationChanged(processStation, filed) 'End Using End Sub ''' ''' 用户选择测试站后回调函数 ''' ''' ''' Private Sub StationChanged(station As ProcessStation, Optional filed As Dictionary(Of String, String) = Nothing) _testLogTableName = TestLogTable.TableName(station.ParentProject.Index, ProcessStation.StationID) '本地新建测试记录表 DbConnector.UtsCreateTestLogTableToLocal(_testLogTableName) '添加预添加列 If filed IsNot Nothing AndAlso filed.Count > 0 Then DbConnector.AddTestLogCols(_testLogTableName, filed) End If '修改测试记录中测试流程 _testResult.TestPlan = _processStation.Packet.FileName 'App日志 _register.ProjectName = _processStation.ParentProject.Name _register.StationName = _processStation.Name _register.TestPlan = _processStation.Packet.FileName _register.AppendTraceLog($"Station Changed,PN:{station.ParentProject.Name},SN:{station.Name}.") _register.AppMsg = $"PN:{_processStation.ParentProject.Name},SN:{_processStation.Name},TP:{_register.TestPlan}" '触发其他页面的站位修改事件 For Each observer As IProcessStation In _observerList observer.StationChanged() Next End Sub ''' ''' 定期更新APP存活时间 ''' ''' Private Sub UpdateAppAliveTime(state As Object) Dim Interval As Integer = 1000 * 60 * 1 '单位ms While _disposedValue = False If (Now - _testChangedTime).TotalMinutes >= 1 AndAlso TestStatusMonitor.TestStatus <> TestStatusMonitor.TestStatusEnum.Testing Then _register.AppStatus = 0 Else _register.AppStatus = 1 End If Try _register.UpdateAliveTime() Catch ex As Exception Console.WriteLine($"UpdateAppAliveTime Error:{ex.Message}") End Try Thread.Sleep(Interval) End While End Sub ''' ''' 保存软件运行日志至数据库 ''' ''' Private Sub SaveServiceLog(state As Object) Dim Interval As Integer = 1000 * 60 * 1 '单位ms While _disposedValue = False SaveServiceLogQueueToDb() Thread.Sleep(Interval) End While End Sub ''' ''' 保存软件运行日志至数据库 ''' Private Sub SaveServiceLogQueueToDb() Try If _register IsNot Nothing Then _register.SaveLogQueueToDb() End If Catch ex As Exception Console.WriteLine($"SaveServiceLog Error:{ex.Message}") End Try End Sub Protected Overridable Sub Dispose(disposing As Boolean) If Not _disposedValue Then If disposing Then '释放托管状态(托管对象) End If '释放未托管的资源(未托管的对象)并替代终结器 '将大型字段设置为 null _disposedValue = True SaveServiceLogQueueToDb() End If End Sub Public Sub Dispose() Implements IDisposable.Dispose ' 不要更改此代码。请将清理代码放入“Dispose(disposing As Boolean)”方法中 Dispose(disposing:=True) GC.SuppressFinalize(Me) End Sub End Class End Namespace