mirror of
				https://gitee.com/ThingsGateway/ThingsGateway.git
				synced 2025-10-31 15:43:59 +08:00 
			
		
		
		
	Compare commits
	
		
			8 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 453817ef86 | ||
|   | 8ce0b981c1 | ||
|   | 4e5c51b54c | ||
|   | 3cc9d31f28 | ||
|   | 10391f869b | ||
|   | fba0723a6d | ||
|   | 2db3f78f0c | ||
|   | badf61fe01 | 
| @@ -683,13 +683,20 @@ public class MachineInfo : IExtend | ||||
|             if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty()) | ||||
|                 Memory = (UInt64)str.TrimEnd(" kB").ToLong(); | ||||
|  | ||||
|             ulong ma = 0; | ||||
|             if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty()) | ||||
|                 AvailableMemory = (UInt64)str.TrimEnd(" kB").ToLong(); | ||||
|             else if (dic.TryGetValue("MemFree", out str) && !str.IsNullOrEmpty()) | ||||
|                 AvailableMemory = | ||||
|                     (UInt64)(str.TrimEnd(" kB").ToLong() + | ||||
|                     dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0 + | ||||
|                     dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0); | ||||
|             { | ||||
|                 ma = (UInt64)(str.TrimEnd(" kB").ToLong()); | ||||
|             } | ||||
|  | ||||
|             //低于3.14内核的版本用 free+cache | ||||
|             var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToLong() ?? 0); | ||||
|             var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToLong() ?? 0); | ||||
|             var bf = (UInt64)(dic["Buffers"]?.TrimEnd(" kB").ToLong() ?? 0); | ||||
|  | ||||
|             var free = mf + mc + bf; | ||||
|  | ||||
|             AvailableMemory = ma > free ? ma : free; | ||||
|         } | ||||
|  | ||||
|         // A2/A4温度获取,Buildroot,CPU温度和主板温度 | ||||
|   | ||||
| @@ -406,22 +406,24 @@ AND sql LIKE '%" + tableName + "%'"); | ||||
|         public override bool CreateDatabase(string databaseName, string databaseDirectory = null) | ||||
|         { | ||||
|             var connString = this.Context.CurrentConnectionConfig.ConnectionString; | ||||
|             var path = Regex.Match(connString, @"[a-z,A-Z]\:\\.+\\").Value; | ||||
|             if (path.IsNullOrEmpty()) | ||||
|  | ||||
|  | ||||
|             // 提取 Data Source=xxx(不管是绝对还是相对路径) | ||||
|             var match = Regex.Match(connString, @"(?i)Data\s+Source\s*=\s*(.+?)(;|$)"); | ||||
|             if (match.Success) | ||||
|             { | ||||
|                 path = Regex.Match(connString, @"\/.+\/").Value; | ||||
|             } | ||||
|             if (path.IsNullOrEmpty()) | ||||
|             { | ||||
|                 path = Regex.Match(connString, @"[a-z,A-Z]\:\\").Value; | ||||
|             } | ||||
|             if (!path.IsNullOrEmpty()) | ||||
|             { | ||||
|                 if (!FileHelper.IsExistDirectory(path)) | ||||
|                 var filePath = match.Groups[1].Value.Trim(); // => ./DB/data.sqlite | ||||
|                 var folderPath = Path.GetDirectoryName(filePath); // => ./DB | ||||
|  | ||||
|                 if (!folderPath.IsNullOrEmpty()) | ||||
|                 { | ||||
|                     FileHelper.CreateDirectory(path); | ||||
|                     if (!FileHelper.IsExistDirectory(folderPath)) | ||||
|                     { | ||||
|                         FileHelper.CreateDirectory(folderPath); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this.Context.Ado.Connection.Open(); | ||||
|             this.Context.Ado.Connection.Close(); | ||||
|             return true; | ||||
|   | ||||
| @@ -31,11 +31,12 @@ | ||||
| 		<PackageReference Include="CsvHelper" Version="33.1.0" /> | ||||
| 		<PackageReference Include="TDengine.Connector" Version="3.1.7" /> | ||||
| 		<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" /> | ||||
| 		<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.20" /> | ||||
| 		<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.21" /> | ||||
| 		<PackageReference Include="System.Data.Common" Version="4.3.0" /> | ||||
| 		<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.0" /> | ||||
| 		<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" /> | ||||
| 		<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> | ||||
| 		<PackageReference Include="System.Formats.Asn1" Version="8.0.2" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| <Project> | ||||
|  | ||||
| 	<PropertyGroup> | ||||
| 		<PluginVersion>10.9.94</PluginVersion> | ||||
| 		<ProPluginVersion>10.9.93</ProPluginVersion> | ||||
| 		<DefaultVersion>10.9.98</DefaultVersion> | ||||
| 		<PluginVersion>10.10.2</PluginVersion> | ||||
| 		<ProPluginVersion>10.10.2</ProPluginVersion> | ||||
| 		<DefaultVersion>10.10.3</DefaultVersion> | ||||
| 		<AuthenticationVersion>2.9.29</AuthenticationVersion> | ||||
| 		<SourceGeneratorVersion>10.9.29</SourceGeneratorVersion> | ||||
| 		<NET8Version>8.0.18</NET8Version> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <Project> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net462;netstandard2.0;net6.0;</TargetFrameworks> | ||||
| 		<TargetFrameworks>net462;netstandard2.0;net6.0;net8.0</TargetFrameworks> | ||||
| 	</PropertyGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -134,6 +134,7 @@ public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcp | ||||
|  | ||||
|     protected override void SafetyDispose(bool disposing) | ||||
|     { | ||||
|         m_transport?.SafeCancel(); | ||||
|         m_transport?.SafeDispose(); | ||||
|         base.SafetyDispose(disposing); | ||||
|     } | ||||
|   | ||||
| @@ -204,6 +204,7 @@ public class UdpSessionChannel : UdpSession, IClientChannel | ||||
|     /// <inheritdoc/> | ||||
|     protected override void SafetyDispose(bool disposing) | ||||
|     { | ||||
|         m_transport?.SafeCancel(); | ||||
|         m_transport?.SafeDispose(); | ||||
|         WaitHandlePool.SafeDispose(); | ||||
|         base.SafetyDispose(disposing); | ||||
|   | ||||
| @@ -24,7 +24,7 @@ namespace ThingsGateway.Foundation; | ||||
| /// <summary> | ||||
| /// 协议基类 | ||||
| /// </summary> | ||||
| public abstract class DeviceBase : DisposableObject, IDevice | ||||
| public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice | ||||
| { | ||||
|     /// <inheritdoc/> | ||||
|     public IChannel Channel { get; private set; } | ||||
| @@ -575,12 +575,18 @@ public abstract class DeviceBase : DisposableObject, IDevice | ||||
|  | ||||
|             var sendOperResult = await SendAsync(command, clientChannel, endPoint, cancellationToken).ConfigureAwait(false); | ||||
|             if (!sendOperResult.IsSuccess) | ||||
|                 throw sendOperResult.Exception ?? new(sendOperResult.ErrorMessage ?? "unknown error"); | ||||
|                 return new MessageBase(sendOperResult); | ||||
|  | ||||
|             waitData.SetCancellationToken(cancellationToken); | ||||
|  | ||||
|             await waitData.WaitAsync(timeout).ConfigureAwait(false); | ||||
|             try | ||||
|             { | ||||
|                 waitData.SetCancellationToken(Channel.ClosedToken); | ||||
|  | ||||
|                 await waitData.WaitAsync(timeout).ConfigureAwait(false); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 return new MessageBase(ex); | ||||
|             } | ||||
|             var result = waitData.Check(); | ||||
|             if (result.IsSuccess) | ||||
|             { | ||||
| @@ -588,33 +594,11 @@ public abstract class DeviceBase : DisposableObject, IDevice | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (cancellationToken.IsCancellationRequested && result.Exception is OperationCanceledException) | ||||
|                 { | ||||
|                     waitData.Reset(); | ||||
|                     waitData.SetCancellationToken(CancellationToken.None); | ||||
|                     await waitData.WaitAsync(timeout).ConfigureAwait(false); | ||||
|                     result = waitData.Check(); | ||||
|                     if (result.IsSuccess) | ||||
|                     { | ||||
|                         return waitData.WaitResult; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         return new MessageBase(result); | ||||
|                     } | ||||
|                 } | ||||
|                 return new MessageBase(result); | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             if (cancellationToken.IsCancellationRequested) | ||||
|             { | ||||
|                 if (!this.DisposedValue) | ||||
|                 { | ||||
|                     await Task.Delay(timeout, CancellationToken.None).ConfigureAwait(false); | ||||
|                 } | ||||
|             } | ||||
|             return new MessageBase(ex); | ||||
|         } | ||||
|         finally | ||||
| @@ -1050,6 +1034,56 @@ public abstract class DeviceBase : DisposableObject, IDevice | ||||
|         base.Dispose(disposing); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override async Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         if (Channel != null) | ||||
|         { | ||||
|             Channel.Starting.Remove(ChannelStarting); | ||||
|             Channel.Stoped.Remove(ChannelStoped); | ||||
|             Channel.Started.Remove(ChannelStarted); | ||||
|             Channel.Stoping.Remove(ChannelStoping); | ||||
|             Channel.ChannelReceived.Remove(ChannelReceived); | ||||
|  | ||||
|             if (Channel.Collects.Count == 1) | ||||
|             { | ||||
|                 if (Channel is ITcpServiceChannel tcpServiceChannel) | ||||
|                 { | ||||
|                     tcpServiceChannel.Clients.ForEach(a => a.WaitHandlePool.SafeDispose()); | ||||
|                 } | ||||
|  | ||||
|                 try | ||||
|                 { | ||||
|                     //只关闭,不释放 | ||||
|                     await Channel.CloseAsync().ConfigureAwait(false); | ||||
|                     if (Channel is IClientChannel client) | ||||
|                     { | ||||
|                         client.WaitHandlePool.SafeDispose(); | ||||
|                     } | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     Logger?.LogWarning(ex); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (Channel is ITcpServiceChannel tcpServiceChannel && this is IDtu dtu) | ||||
|                 { | ||||
|                     if (tcpServiceChannel.TryGetClient($"ID={dtu.DtuId}", out var client)) | ||||
|                     { | ||||
|                         client.WaitHandlePool?.SafeDispose(); | ||||
|                         await client.CloseAsync().ConfigureAwait(false); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Channel.Collects.Remove(this); | ||||
|         } | ||||
|  | ||||
|         _deviceLogger?.TryDispose(); | ||||
|         base.Dispose(disposing); | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     public virtual Action<IPluginManager> ConfigurePlugins(TouchSocketConfig config) | ||||
|     { | ||||
| @@ -1067,4 +1101,5 @@ public abstract class DeviceBase : DisposableObject, IDevice | ||||
|         return a => { }; | ||||
|     } | ||||
|     public abstract ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadAsync(object state, CancellationToken cancellationToken = default); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -15,7 +15,7 @@ namespace ThingsGateway.Foundation; | ||||
| /// <summary> | ||||
| /// 协议设备接口 | ||||
| /// </summary> | ||||
| public interface IDevice : IDisposable, IDisposableObject | ||||
| public interface IDevice : IDisposable, IDisposableObject, IAsyncDisposable | ||||
| { | ||||
|     #region 属性 | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,73 @@ | ||||
| // ------------------------------------------------------------------------------ | ||||
| // 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充 | ||||
| // 此代码版权(除特别声明外的代码)归作者本人Diego所有 | ||||
| // 源代码使用协议遵循本仓库的开源协议及附加协议 | ||||
| // Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway | ||||
| // Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway | ||||
| // 使用文档:https://thingsgateway.cn/ | ||||
| // QQ群:605534569 | ||||
| // ------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway; | ||||
|  | ||||
|  | ||||
| public static class DisposableExtensions | ||||
| { | ||||
|     #region IDisposable | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 安全性释放(不用判断对象是否为空)。不会抛出任何异常。 | ||||
|     /// </summary> | ||||
|     /// <param name="dis"></param> | ||||
|     /// <returns>释放状态,当对象为<see langword="null"/>,或者已被释放时,均会返回<see cref="Result.Success"/>,只有实际在释放时遇到异常时,才显示其他状态。</returns> | ||||
|     public static async Task<Result> SafeDisposeAsync(this IAsyncDisposable dis) | ||||
|     { | ||||
|         if (dis == default) | ||||
|         { | ||||
|             return Result.Success; | ||||
|         } | ||||
|         try | ||||
|         { | ||||
|             await dis.DisposeAsync().ConfigureAwait(false); | ||||
|             return Result.Success; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return Result.FromException(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #endregion IDisposable | ||||
|  | ||||
|  | ||||
| #if NET8_0_OR_GREATER | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 安全地取消 <see cref="CancellationTokenSource"/>,并返回操作结果。 | ||||
|     /// </summary> | ||||
|     /// <param name="tokenSource">要取消的 <see cref="CancellationTokenSource"/>。</param> | ||||
|     /// <returns>一个 <see cref="Result"/> 对象,表示操作的结果。</returns> | ||||
|     public static async Task<Result> SafeCancelAsync(this CancellationTokenSource tokenSource) | ||||
|     { | ||||
|         if (tokenSource is null) | ||||
|         { | ||||
|             return Result.Success; | ||||
|         } | ||||
|         try | ||||
|         { | ||||
|             await tokenSource.CancelAsync().ConfigureAwait(false); | ||||
|             return Result.Success; | ||||
|         } | ||||
|         catch (ObjectDisposedException) | ||||
|         { | ||||
|             return Result.Disposed; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             return Result.FromException(ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| #endif | ||||
| } | ||||
| @@ -9,6 +9,7 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Foundation; | ||||
| #pragma warning disable CA1851 | ||||
|  | ||||
| public static class PackHelpers | ||||
| { | ||||
|   | ||||
| @@ -0,0 +1,109 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 | ||||
| //  CSDN博客:https://blog.csdn.net/qq_40374647 | ||||
| //  哔哩哔哩视频:https://space.bilibili.com/94253567 | ||||
| //  Gitee源代码仓库:https://gitee.com/RRQM_Home | ||||
| //  Github源代码仓库:https://github.com/RRQM | ||||
| //  API首页:https://touchsocket.net/ | ||||
| //  交流QQ群:234762506 | ||||
| //  感谢您的下载和使用 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace ThingsGateway.Foundation; | ||||
|  | ||||
| /// <summary> | ||||
| /// 具有释放的对象。内部实现了<see cref="GC.SuppressFinalize(object)"/>,但不包括析构函数相关。 | ||||
| /// </summary> | ||||
| public abstract partial class AsyncAndSyncDisposableObject : | ||||
|    IDisposableObject, | ||||
|    IAsyncDisposable | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 判断当前对象是否已经被释放。 | ||||
|     /// 如果已经被释放,则抛出<see cref="ObjectDisposedException"/>异常。 | ||||
|     /// </summary> | ||||
|     /// <exception cref="ObjectDisposedException">当对象已经被释放时抛出此异常</exception> | ||||
|     [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|     protected void ThrowIfDisposed() | ||||
|     { | ||||
|         // 检查对象是否已经被释放 | ||||
|         if (this.m_disposedValue) | ||||
|         { | ||||
|             // 如果对象已被释放,抛出ObjectDisposedException异常 | ||||
|             throw new ObjectDisposedException($"The object instance with type {this.GetType().FullName} has been released"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private int m_count = 0; | ||||
|     private int m_asyncCount = 0; | ||||
|     /// <summary> | ||||
|     /// 判断是否已释放。 | ||||
|     /// </summary> | ||||
|     private volatile bool m_disposedValue; | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public bool DisposedValue => this.m_disposedValue; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 处置资源 | ||||
|     /// </summary> | ||||
|     /// <param name="disposing">一个值,表示是否释放托管资源</param> | ||||
|     protected virtual void Dispose(bool disposing) | ||||
|     { | ||||
|         // 标记当前对象为已处置状态 | ||||
|         this.m_disposedValue = true; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 释放资源。内部已经处理了<see cref="GC.SuppressFinalize(object)"/> | ||||
|     /// </summary> | ||||
|     public void Dispose() | ||||
|     { | ||||
|         if (this.DisposedValue) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (Interlocked.Increment(ref this.m_count) == 1) | ||||
|         { | ||||
|             this.Dispose(disposing: true); | ||||
|         } | ||||
|         GC.SuppressFinalize(this); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 处置资源 | ||||
|     /// </summary> | ||||
|     /// <param name="disposing">一个值,表示是否释放托管资源</param> | ||||
|     protected virtual Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         // 标记当前对象为已处置状态 | ||||
|         this.m_disposedValue = true; | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 释放资源。内部已经处理了<see cref="GC.SuppressFinalize(object)"/> | ||||
|     /// </summary> | ||||
|     public async ValueTask DisposeAsync() | ||||
|     { | ||||
|         if (this.DisposedValue) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         //if (Interlocked.Increment(ref this.m_count) == 1) | ||||
|         //{ | ||||
|         //    this.Dispose(disposing: true); | ||||
|         //} | ||||
|  | ||||
|         if (Interlocked.Increment(ref this.m_asyncCount) == 1) | ||||
|         { | ||||
|             await this.DisposeAsync(disposing: true).ConfigureAwait(false); | ||||
|         } | ||||
|         GC.SuppressFinalize(this); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,108 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| //  此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 | ||||
| //  源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 | ||||
| //  CSDN博客:https://blog.csdn.net/qq_40374647 | ||||
| //  哔哩哔哩视频:https://space.bilibili.com/94253567 | ||||
| //  Gitee源代码仓库:https://gitee.com/RRQM_Home | ||||
| //  Github源代码仓库:https://github.com/RRQM | ||||
| //  API首页:https://touchsocket.net/ | ||||
| //  交流QQ群:234762506 | ||||
| //  感谢您的下载和使用 | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| using System.Runtime.CompilerServices; | ||||
|  | ||||
| namespace ThingsGateway.Foundation; | ||||
|  | ||||
| /// <summary> | ||||
| /// 具有释放的对象。内部实现了<see cref="GC.SuppressFinalize(object)"/>,但不包括析构函数相关。 | ||||
| /// </summary> | ||||
| public abstract partial class AsyncDisposableObject : | ||||
|    //IDisposableObject, | ||||
|    IAsyncDisposable | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 判断当前对象是否已经被释放。 | ||||
|     /// 如果已经被释放,则抛出<see cref="ObjectDisposedException"/>异常。 | ||||
|     /// </summary> | ||||
|     /// <exception cref="ObjectDisposedException">当对象已经被释放时抛出此异常</exception> | ||||
|     [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
|     protected void ThrowIfDisposed() | ||||
|     { | ||||
|         // 检查对象是否已经被释放 | ||||
|         if (this.m_disposedValue) | ||||
|         { | ||||
|             // 如果对象已被释放,抛出ObjectDisposedException异常 | ||||
|             throw new ObjectDisposedException($"The object instance with type {this.GetType().FullName} has been released"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private int m_asyncCount = 0; | ||||
|     /// <summary> | ||||
|     /// 判断是否已释放。 | ||||
|     /// </summary> | ||||
|     private volatile bool m_disposedValue; | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public bool DisposedValue => this.m_disposedValue; | ||||
|  | ||||
|     ///// <summary> | ||||
|     ///// 处置资源 | ||||
|     ///// </summary> | ||||
|     ///// <param name="disposing">一个值,表示是否释放托管资源</param> | ||||
|     //protected virtual void Dispose(bool disposing) | ||||
|     //{ | ||||
|     //    // 标记当前对象为已处置状态 | ||||
|     //    this.m_disposedValue = true; | ||||
|     //} | ||||
|  | ||||
|     ///// <summary> | ||||
|     ///// 释放资源。内部已经处理了<see cref="GC.SuppressFinalize(object)"/> | ||||
|     ///// </summary> | ||||
|     //public void Dispose() | ||||
|     //{ | ||||
|     //    if (this.DisposedValue) | ||||
|     //    { | ||||
|     //        return; | ||||
|     //    } | ||||
|  | ||||
|     //    if (Interlocked.Increment(ref this.m_count) == 1) | ||||
|     //    { | ||||
|     //        this.Dispose(disposing: true); | ||||
|     //    } | ||||
|     //    GC.SuppressFinalize(this); | ||||
|     //} | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 处置资源 | ||||
|     /// </summary> | ||||
|     /// <param name="disposing">一个值,表示是否释放托管资源</param> | ||||
|     protected virtual Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         // 标记当前对象为已处置状态 | ||||
|         this.m_disposedValue = true; | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 释放资源。内部已经处理了<see cref="GC.SuppressFinalize(object)"/> | ||||
|     /// </summary> | ||||
|     public async ValueTask DisposeAsync() | ||||
|     { | ||||
|         if (this.DisposedValue) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         //if (Interlocked.Increment(ref this.m_count) == 1) | ||||
|         //{ | ||||
|         //    this.Dispose(disposing: true); | ||||
|         //} | ||||
|  | ||||
|         if (Interlocked.Increment(ref this.m_asyncCount) == 1) | ||||
|         { | ||||
|             await this.DisposeAsync(disposing: true).ConfigureAwait(false); | ||||
|         } | ||||
|         GC.SuppressFinalize(this); | ||||
|     } | ||||
| } | ||||
| @@ -14,48 +14,88 @@ namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| public class AsyncReadWriteLock | ||||
| { | ||||
|     private readonly int _writeReadRatio = 3; // 写3次会允许1次读,但写入也不会被阻止,具体协议取决于插件协议实现 | ||||
|     public AsyncReadWriteLock(int writeReadRatio) | ||||
|     { | ||||
|         _writeReadRatio = writeReadRatio; | ||||
|     } | ||||
|     private AsyncAutoResetEvent _readerLock = new AsyncAutoResetEvent(false); // 控制读计数 | ||||
|     private long _writerCount = 0; // 当前活跃的写线程数 | ||||
|     private long _readerCount = 0; // 当前被阻塞的读线程数 | ||||
|     private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); | ||||
|     /// <summary> | ||||
|     /// 获取读锁,支持多个线程并发读取,但写入时会阻止所有读取。 | ||||
|     /// </summary> | ||||
|     public async Task<CancellationToken> ReaderLockAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|  | ||||
|         if (Interlocked.Read(ref _writerCount) > 0) | ||||
|         { | ||||
|             Interlocked.Increment(ref _readerCount); | ||||
|  | ||||
|             // 第一个读者需要获取写入锁,防止写操作 | ||||
|             await _readerLock.WaitOneAsync(cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|             Interlocked.Decrement(ref _readerCount); | ||||
|  | ||||
|         } | ||||
|         return _cancellationTokenSource.Token; | ||||
|     } | ||||
|  | ||||
|     public bool WriteWaited => _writerCount > 0; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 获取写锁,阻止所有读取。 | ||||
|     /// </summary> | ||||
|     public IDisposable WriterLock() | ||||
|     public async Task<IDisposable> WriterLockAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|  | ||||
|         if (Interlocked.Increment(ref _writerCount) == 1) | ||||
|         { | ||||
|             var cancellationTokenSource = _cancellationTokenSource; | ||||
|             _cancellationTokenSource = new(); | ||||
|             cancellationTokenSource.Cancel();//取消读取 | ||||
|             await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取 | ||||
|             cancellationTokenSource.SafeDispose(); | ||||
|         } | ||||
|  | ||||
|         return new Writer(this); | ||||
|     } | ||||
|  | ||||
|     private void ReleaseWriter() | ||||
|     { | ||||
|         if (Interlocked.Decrement(ref _writerCount) == 0) | ||||
|         var writerCount = Interlocked.Decrement(ref _writerCount); | ||||
|         if (writerCount == 0) | ||||
|         { | ||||
|             var resetEvent = _readerLock; | ||||
|             _readerLock = new(false); | ||||
|             Interlocked.Exchange(ref _writeSinceLastReadCount, 0); | ||||
|             resetEvent.SetAll(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|  | ||||
|             // 读写占空比, 用于控制写操作与读操作的比率。该比率 n 次写入操作会执行一次读取操作。即使在应用程序执行大量的连续写入操作时,也必须确保足够的读取数据处理时间。相对于更加均衡的读写数据流而言,该特点使得外部写入可连续无顾忌操作 | ||||
|  | ||||
|             if (_writeReadRatio > 0) | ||||
|             { | ||||
|                 if (Interlocked.Read(ref _readerCount) > 0) | ||||
|                 { | ||||
|                     var count = Interlocked.Increment(ref _writeSinceLastReadCount); | ||||
|                     if (count >= _writeReadRatio) | ||||
|                     { | ||||
|                         Interlocked.Exchange(ref _writeSinceLastReadCount, 0); | ||||
|                         _readerLock.Set(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 _readerLock.Set(); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private int _writeSinceLastReadCount = 0; | ||||
|     private struct Writer : IDisposable | ||||
|     { | ||||
|         private readonly AsyncReadWriteLock _lock; | ||||
|   | ||||
| @@ -59,10 +59,10 @@ public abstract class BusinessBaseWithCacheAlarm : BusinessBaseWithCache | ||||
|  | ||||
|         await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false); | ||||
|     } | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         GlobalData.AlarmChangedEvent -= AlarmValueChange; | ||||
|         base.Dispose(disposing); | ||||
|         return base.DisposeAsync(disposing); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 当报警值发生变化时触发此事件处理方法。该方法内部会检查是否需要进行报警上传,如果需要,则调用 <see cref="AlarmChange(AlarmVariable)"/> 方法。 | ||||
|   | ||||
| @@ -130,7 +130,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache | ||||
|     /// <summary> | ||||
|     /// 释放资源方法 | ||||
|     /// </summary> | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         // 解绑事件 | ||||
|         GlobalData.AlarmChangedEvent -= AlarmValueChange; | ||||
| @@ -142,7 +142,7 @@ public abstract class BusinessBaseWithCacheInterval : BusinessBaseWithCache | ||||
|         _memoryDevModelQueue.Clear(); | ||||
|         _memoryVarModelQueue.Clear(); | ||||
|         _memoryVarModelsQueue.Clear(); | ||||
|         base.Dispose(disposing); | ||||
|         return base.DisposeAsync(disposing); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|   | ||||
| @@ -194,6 +194,8 @@ public abstract class CollectBase : DriverBase, IRpcDriver | ||||
|  | ||||
|         // 从插件服务中获取当前设备关联的驱动方法信息列表 | ||||
|         DriverMethodInfos = GlobalData.PluginService.GetDriverMethodInfos(device.PluginName, this); | ||||
|  | ||||
|         ReadWriteLock = new(CollectProperties.DutyCycle); | ||||
|     } | ||||
|  | ||||
|     public virtual string GetAddressDescription() | ||||
| @@ -474,7 +476,7 @@ public abstract class CollectBase : DriverBase, IRpcDriver | ||||
|     /// <returns></returns> | ||||
|     protected abstract Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables); | ||||
|  | ||||
|     protected AsyncReadWriteLock ReadWriteLock = new(); | ||||
|     protected AsyncReadWriteLock ReadWriteLock; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 采集驱动读取,读取成功后直接赋值变量 | ||||
| @@ -565,7 +567,8 @@ public abstract class CollectBase : DriverBase, IRpcDriver | ||||
|  | ||||
|         ConcurrentDictionary<string, OperResult<object>> operResults = new(); | ||||
|  | ||||
|         using var writeLock = ReadWriteLock.WriterLock(); | ||||
|  | ||||
|         using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); | ||||
|         var list = writeInfoLists | ||||
|         .Where(a => !results.Any(b => b.Key == a.Key.Name)) | ||||
|         .ToDictionary(item => item.Key, item => item.Value).ToArray(); | ||||
|   | ||||
| @@ -47,10 +47,11 @@ public abstract class CollectFoundationBase : CollectBase | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override async Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         FoundationDevice?.Dispose(); | ||||
|         base.Dispose(disposing); | ||||
|         if (FoundationDevice != null) | ||||
|             await FoundationDevice.SafeDisposeAsync().ConfigureAwait(false); | ||||
|         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 开始通讯执行的方法 | ||||
| @@ -175,7 +176,7 @@ public abstract class CollectFoundationBase : CollectBase | ||||
|     /// <returns></returns> | ||||
|     protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||
|     { | ||||
|         using var writeLock = ReadWriteLock.WriterLock(); | ||||
|         using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); | ||||
|         // 检查协议是否为空,如果为空则抛出异常 | ||||
|         if (FoundationDevice == null) | ||||
|             throw new NotSupportedException(); | ||||
|   | ||||
| @@ -31,6 +31,11 @@ public abstract class CollectPropertyBase : DriverPropertyBase | ||||
|     /// 失败重试次数,默认3 | ||||
|     /// </summary> | ||||
|     public virtual int RetryCount { get; set; } = 3; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 读写占空比 | ||||
|     /// </summary> | ||||
|     public virtual int DutyCycle { get; set; } = 3; | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| @@ -45,4 +50,8 @@ public abstract class CollectPropertyRetryBase : CollectPropertyBase | ||||
|     /// </summary> | ||||
|     [DynamicProperty] | ||||
|     public override int RetryCount { get; set; } = 3; | ||||
|  | ||||
|     [DynamicProperty(Remark = "n 次写入操作会执行一次读取")] | ||||
|     public override int DutyCycle { get; set; } = 3; | ||||
|  | ||||
| } | ||||
| @@ -26,11 +26,12 @@ namespace ThingsGateway.Gateway.Application; | ||||
| /// <summary> | ||||
| /// 插件基类 | ||||
| /// </summary> | ||||
| public abstract class DriverBase : DisposableObject, IDriver | ||||
| public abstract class DriverBase : AsyncDisposableObject, IDriver | ||||
| { | ||||
|     /// <inheritdoc cref="DriverBase"/> | ||||
|     public DriverBase() | ||||
|     { | ||||
|  | ||||
|         Localizer = App.CreateLocalizerByType(typeof(DriverBase))!; | ||||
|     } | ||||
|  | ||||
| @@ -39,8 +40,7 @@ public abstract class DriverBase : DisposableObject, IDriver | ||||
|     /// <summary> | ||||
|     /// 当前设备 | ||||
|     /// </summary> | ||||
|     public DeviceRuntime? CurrentDevice => WeakReferenceCurrentDevice?.TryGetTarget(out var target) == true ? target : null; | ||||
|     private WeakReference<DeviceRuntime> WeakReferenceCurrentDevice { get; set; } | ||||
|     public DeviceRuntime? CurrentDevice { get; private set; } | ||||
|     /// <summary> | ||||
|     /// 当前设备Id | ||||
|     /// </summary> | ||||
| @@ -208,7 +208,7 @@ public abstract class DriverBase : DisposableObject, IDriver | ||||
|     /// </summary> | ||||
|     internal void InitDevice(DeviceRuntime device) | ||||
|     { | ||||
|         WeakReferenceCurrentDevice = new WeakReference<DeviceRuntime>(device); | ||||
|         CurrentDevice = device; | ||||
|  | ||||
|         _logger = App.RootServices.GetService<Microsoft.Extensions.Logging.ILoggerFactory>().CreateLogger($"Driver[{CurrentDevice.Name}]"); | ||||
|  | ||||
| @@ -313,38 +313,44 @@ public abstract class DriverBase : DisposableObject, IDriver | ||||
|  | ||||
|     protected abstract List<IScheduledTask> ProtectedGetTasks(CancellationToken cancellationToken); | ||||
|  | ||||
|     protected object stopLock = new(); | ||||
|     protected WaitLock stopLock = new(nameof(DriverBase)); | ||||
|     /// <summary> | ||||
|     /// 已停止任务,释放插件 | ||||
|     /// </summary> | ||||
|     internal virtual void Stop() | ||||
|     internal virtual async Task StopAsync() | ||||
|     { | ||||
|         if (!DisposedValue) | ||||
|         { | ||||
|             lock (stopLock) | ||||
|             await stopLock.WaitAsync().ConfigureAwait(false); | ||||
|             try | ||||
|             { | ||||
|  | ||||
|  | ||||
|                 if (!DisposedValue) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         // 执行资源释放操作 | ||||
|                         Dispose(); | ||||
|                     } | ||||
|                     catch (Exception ex) | ||||
|                     { | ||||
|                         // 记录 Dispose 方法执行失败的错误信息 | ||||
|                         LogMessage?.LogError(ex, "Dispose"); | ||||
|                     } | ||||
|  | ||||
|                     // 执行资源释放操作 | ||||
|                     await this.SafeDisposeAsync().ConfigureAwait(false); | ||||
|  | ||||
|                     // 记录设备线程已停止的信息 | ||||
|                     LogMessage?.LogInformation(string.Format(AppResource.DeviceTaskStop, DeviceName)); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 // 记录 Dispose 方法执行失败的错误信息 | ||||
|                 LogMessage?.LogError(ex, "Dispose"); | ||||
|             } | ||||
|             finally | ||||
|             { | ||||
|                 stopLock.Release(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override async Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         base.Dispose(disposing); | ||||
|         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||
|         if (TaskSchedulerLoop != null) | ||||
|         { | ||||
|             lock (TaskSchedulerLoop) | ||||
|   | ||||
| @@ -14,7 +14,7 @@ using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application | ||||
| { | ||||
|     public interface IDriver : IDisposable | ||||
|     public interface IDriver : IAsyncDisposable | ||||
|     { | ||||
|         bool DisposedValue { get; } | ||||
|         ChannelRuntime CurrentChannel { get; } | ||||
|   | ||||
| @@ -17,6 +17,7 @@ using TouchSocket.Core; | ||||
| using TouchSocket.Sockets; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
| #pragma warning disable CS0649 | ||||
|  | ||||
| /// <summary> | ||||
| /// 通道表 | ||||
|   | ||||
| @@ -17,6 +17,7 @@ using System.ComponentModel.DataAnnotations; | ||||
| using ThingsGateway.NewLife.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
| #pragma warning disable CS0649 | ||||
|  | ||||
| /// <summary> | ||||
| /// 设备表 | ||||
|   | ||||
| @@ -16,6 +16,7 @@ using System.Collections.Concurrent; | ||||
| using System.ComponentModel.DataAnnotations; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
| #pragma warning disable CS0649 | ||||
|  | ||||
| /// <summary> | ||||
| /// 设备变量表 | ||||
|   | ||||
| @@ -295,7 +295,8 @@ | ||||
|     "RetryCount": "RetryCount" | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.CollectPropertyRetryBase": { | ||||
|     "RetryCount": "RetryCount" | ||||
|     "RetryCount": "RetryCount", | ||||
|     "DutyCycle": "DutyCycle" | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.ControlController": { | ||||
|     "BatchSaveChannelAsync": "BatchSaveChannel", | ||||
|   | ||||
| @@ -294,7 +294,8 @@ | ||||
|     "RetryCount": "失败重试次数" | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.CollectPropertyRetryBase": { | ||||
|     "RetryCount": "失败重试次数" | ||||
|     "RetryCount": "失败重试次数", | ||||
|     "DutyCycle": "占空比" | ||||
|   }, | ||||
|   "ThingsGateway.Gateway.Application.ControlController": { | ||||
|     "BatchSaveChannelAsync": "保存通道", | ||||
|   | ||||
| @@ -12,9 +12,10 @@ using Microsoft.Extensions.Logging; | ||||
|  | ||||
| using ThingsGateway.Common.Extension; | ||||
| using ThingsGateway.Gateway.Application.Extensions; | ||||
| using ThingsGateway.NewLife; | ||||
| using ThingsGateway.NewLife.Extension; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Gateway.Application; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -44,7 +45,7 @@ internal sealed class AlarmTask : IDisposable | ||||
|     public void Dispose() | ||||
|     { | ||||
|         StopTask(); | ||||
|         scheduledTask?.TryDispose(); | ||||
|         scheduledTask?.SafeDispose(); | ||||
|     } | ||||
|  | ||||
|     #region 核心实现 | ||||
|   | ||||
| @@ -43,7 +43,7 @@ internal sealed class ChannelThreadManage : IChannelThreadManage | ||||
|               { | ||||
|                   if (!DeviceThreadManages.TryRemove(channelId, out var deviceThreadManage)) return; | ||||
|  | ||||
|                   await deviceThreadManage.DisposeAsync().ConfigureAwait(false); | ||||
|                   await deviceThreadManage.SafeDisposeAsync().ConfigureAwait(false); | ||||
|               } | ||||
|               catch (Exception ex) | ||||
|               { | ||||
|   | ||||
| @@ -326,7 +326,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage | ||||
|                     { | ||||
|                         try | ||||
|                         { | ||||
|                             await oldCts.CancelAsync().ConfigureAwait(false); | ||||
|                             await oldCts.SafeCancelAsync().ConfigureAwait(false); | ||||
|                             oldCts.SafeDispose(); | ||||
|                         } | ||||
|                         catch | ||||
| @@ -340,7 +340,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage | ||||
|                 CancellationTokenSources.TryAdd(driver.DeviceId, cts); | ||||
|  | ||||
|                 _ = Task.Factory.StartNew((state) => DriverStart(state, token), driver, token, TaskCreationOptions.None, TaskScheduler.Default); | ||||
|             }).ConfigureAwait(false); | ||||
|             }, App.HostApplicationLifetime.ApplicationStopping).ConfigureAwait(false); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
| @@ -393,45 +393,48 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage | ||||
|         try | ||||
|         { | ||||
|             ConcurrentList<VariableRuntime> saveVariableRuntimes = new(); | ||||
|             deviceIds.ParallelForEach((deviceId) => | ||||
|             { | ||||
|                 var now = DateTime.Now; | ||||
|                 // 查找具有指定设备ID的驱动程序对象 | ||||
|                 if (Drivers.TryRemove(deviceId, out var driver)) | ||||
|                 { | ||||
|                     driver.CurrentDevice.SetDeviceStatus(now, false, "Communication connection has been removed"); | ||||
|                     if (IsCollectChannel == true) | ||||
|                     { | ||||
|                         foreach (var a in driver.IdVariableRuntimes) | ||||
|                         { | ||||
|                             a.Value.SetValue(a.Value.Value, now, false); | ||||
|                             a.Value.SetErrorMessage("Communication connection has been removed"); | ||||
|                             if (a.Value.SaveValue && !a.Value.DynamicVariable) | ||||
|                             { | ||||
|                                 saveVariableRuntimes.Add(a.Value); | ||||
|                             } | ||||
|                         } | ||||
|             await deviceIds.ParallelForEachAsync(async (deviceId, cancellationToken) => | ||||
|              { | ||||
|                  var now = DateTime.Now; | ||||
|                  // 查找具有指定设备ID的驱动程序对象 | ||||
|                  if (Drivers.TryRemove(deviceId, out var driver)) | ||||
|                  { | ||||
|                      driver.CurrentDevice.SetDeviceStatus(now, false, "Communication connection has been removed"); | ||||
|                      if (IsCollectChannel == true) | ||||
|                      { | ||||
|                          foreach (var a in driver.IdVariableRuntimes) | ||||
|                          { | ||||
|                              a.Value.SetValue(a.Value.Value, now, false); | ||||
|                              a.Value.SetErrorMessage("Communication connection has been removed"); | ||||
|                              if (a.Value.SaveValue && !a.Value.DynamicVariable) | ||||
|                              { | ||||
|                                  saveVariableRuntimes.Add(a.Value); | ||||
|                              } | ||||
|                          } | ||||
|  | ||||
|                     } | ||||
|                      } | ||||
|  | ||||
|                 } | ||||
|                  } | ||||
|  | ||||
|                 // 取消驱动程序的操作 | ||||
|                 if (CancellationTokenSources.TryRemove(deviceId, out var token)) | ||||
|                 { | ||||
|                     if (token != null) | ||||
|                     { | ||||
|                         driver.Stop(); | ||||
|                         token.Cancel(); | ||||
|                         token.Dispose(); | ||||
|                     } | ||||
|                 } | ||||
|                  // 取消驱动程序的操作 | ||||
|                  if (CancellationTokenSources.TryRemove(deviceId, out var token)) | ||||
|                  { | ||||
|                      if (token != null) | ||||
|                      { | ||||
|                          await token.SafeCancelAsync().ConfigureAwait(false); | ||||
|                          token.SafeDispose(); | ||||
|                          if (driver != null) | ||||
|                          { | ||||
|                              await driver.StopAsync().ConfigureAwait(false); | ||||
|                          } | ||||
|                      } | ||||
|                  } | ||||
|  | ||||
|                 if (DriverTasks.TryRemove(deviceId, out var task)) | ||||
|                 { | ||||
|                     task.Stop(); | ||||
|                 } | ||||
|             }); | ||||
|                  if (DriverTasks.TryRemove(deviceId, out var task)) | ||||
|                  { | ||||
|                      task.Stop(); | ||||
|                  } | ||||
|              }).ConfigureAwait(false); | ||||
|  | ||||
|             await Task.Delay(100).ConfigureAwait(false); | ||||
|  | ||||
| @@ -796,7 +799,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage | ||||
|         Disposed = true; | ||||
|         try | ||||
|         { | ||||
|             await CancellationTokenSource.CancelAsync().ConfigureAwait(false); | ||||
|             await CancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); | ||||
|             CancellationTokenSource.SafeDispose(); | ||||
|             GlobalData.DeviceStatusChangeEvent -= GlobalData_DeviceStatusChangeEvent; | ||||
|             await NewDeviceLock.WaitAsync().ConfigureAwait(false); | ||||
|   | ||||
| @@ -78,7 +78,7 @@ internal sealed class GatewayMonitorHostedService : BackgroundService, IGatewayM | ||||
|  | ||||
|     public override async Task StopAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         await ChannelThreadManage.DisposeAsync().ConfigureAwait(false); | ||||
|         await ChannelThreadManage.SafeDisposeAsync().ConfigureAwait(false); | ||||
|         await base.StopAsync(cancellationToken).ConfigureAwait(false); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -312,10 +312,10 @@ internal sealed class RedundancyTask : IRpcDriver, IAsyncDisposable | ||||
|     { | ||||
|         await StopTaskAsync().ConfigureAwait(false); | ||||
|         TextLogger?.TryDispose(); | ||||
|         scheduledTask?.TryDispose(); | ||||
|         scheduledTask?.SafeDispose(); | ||||
|  | ||||
|         _tcpDmtpService?.TryDispose(); | ||||
|         _tcpDmtpClient?.TryDispose(); | ||||
|         _tcpDmtpService?.SafeDispose(); | ||||
|         _tcpDmtpClient?.SafeDispose(); | ||||
|         _tcpDmtpService = null; | ||||
|         _tcpDmtpClient = null; | ||||
|     } | ||||
|   | ||||
| @@ -78,12 +78,12 @@ internal sealed class PluginService : IPluginService | ||||
|  | ||||
|     public Type GetDebugUI(string pluginName) | ||||
|     { | ||||
|         using var driver = GetDriver(pluginName); | ||||
|         var driver = GetDriver(pluginName); | ||||
|         return driver?.DriverDebugUIType; | ||||
|     } | ||||
|     public Type GetAddressUI(string pluginName) | ||||
|     { | ||||
|         using var driver = GetDriver(pluginName); | ||||
|         var driver = GetDriver(pluginName); | ||||
|         return driver?.DriverVariableAddressUIType; | ||||
|     } | ||||
|  | ||||
| @@ -167,7 +167,6 @@ internal sealed class PluginService : IPluginService | ||||
|         { | ||||
|             string cacheKey = $"{nameof(PluginService)}_{nameof(GetDriverMethodInfos)}_{CultureInfo.CurrentUICulture.Name}"; | ||||
|             // 如果未提供驱动基类对象,则尝试根据插件名称获取驱动对象 | ||||
|             var dispose = driver == null; // 标记是否需要释放驱动对象 | ||||
|             driver ??= GetDriver(pluginName); // 如果未提供驱动对象,则根据插件名称获取驱动对象 | ||||
|  | ||||
|             // 检查插件名称是否为空或null | ||||
| @@ -183,10 +182,10 @@ internal sealed class PluginService : IPluginService | ||||
|             } | ||||
|  | ||||
|             // 如果未从缓存中获取到指定插件的属性信息,则尝试从驱动基类对象中获取 | ||||
|             return SetDriverMethodInfosCache(driver, pluginName, cacheKey, dispose); // 获取并设置属性信息缓存 | ||||
|             return SetDriverMethodInfosCache(driver, pluginName, cacheKey); // 获取并设置属性信息缓存 | ||||
|  | ||||
|             // 用于设置驱动方法信息缓存的内部方法 | ||||
|             List<DriverMethodInfo> SetDriverMethodInfosCache(IDriver driver, string pluginName, string cacheKey, bool dispose) | ||||
|             List<DriverMethodInfo> SetDriverMethodInfosCache(IDriver driver, string pluginName, string cacheKey) | ||||
|             { | ||||
|                 // 获取驱动对象的方法信息,并筛选出带有 DynamicMethodAttribute 特性的方法 | ||||
|                 var dependencyPropertyWithInfos = driver.GetType().GetMethods()?.SelectMany(it => | ||||
| @@ -206,10 +205,6 @@ internal sealed class PluginService : IPluginService | ||||
|                 var result = dependencyPropertyWithInfos.ToList(); | ||||
|                 App.CacheService.HashAdd(cacheKey, pluginName, result); | ||||
|  | ||||
|                 // 如果是通过方法内部创建的驱动对象,则在方法执行完成后释放该驱动对象 | ||||
|                 if (dispose) | ||||
|                     driver.SafeDispose(); | ||||
|  | ||||
|                 // 返回获取到的属性信息字典 | ||||
|                 return result; | ||||
|             } | ||||
| @@ -228,7 +223,6 @@ internal sealed class PluginService : IPluginService | ||||
|         { | ||||
|             string cacheKey = $"{nameof(PluginService)}_{nameof(GetDriverPropertyTypes)}_{CultureInfo.CurrentUICulture.Name}"; | ||||
|  | ||||
|             var dispose = driver == null; | ||||
|             driver ??= GetDriver(pluginName); // 如果 driver 为 null, 获取驱动实例 | ||||
|             // 检查插件名称是否为空或空字符串 | ||||
|             if (!pluginName.IsNullOrEmpty()) | ||||
| @@ -245,17 +239,15 @@ internal sealed class PluginService : IPluginService | ||||
|             } | ||||
|             // 如果缓存中不存在该插件的数据,则重新获取并缓存 | ||||
|  | ||||
|             return (SetCache(driver, pluginName, cacheKey, dispose), driver.DriverProperties, driver.DriverPropertyUIType); // 调用 SetCache 方法进行缓存并返回结果 | ||||
|             return (SetCache(driver, pluginName, cacheKey), driver.DriverProperties, driver.DriverPropertyUIType); // 调用 SetCache 方法进行缓存并返回结果 | ||||
|  | ||||
|             // 定义 SetCache 方法,用于设置缓存并返回 | ||||
|             IEnumerable<IEditorItem> SetCache(IDriver driver, string pluginName, string cacheKey, bool dispose) | ||||
|             IEnumerable<IEditorItem> SetCache(IDriver driver, string pluginName, string cacheKey) | ||||
|             { | ||||
|                 var editorItems = PluginServiceUtil.GetEditorItems(driver.DriverProperties?.GetType()).ToList(); | ||||
|                 // 将结果存入缓存中,键为插件名称 | ||||
|                 App.CacheService.HashAdd(cacheKey, pluginName, editorItems); | ||||
|                 // 如果 dispose 参数为 true,则释放 driver 对象 | ||||
|                 if (dispose) | ||||
|                     driver.SafeDispose(); | ||||
|  | ||||
|                 return editorItems; | ||||
|             } | ||||
|         } | ||||
| @@ -291,7 +283,6 @@ internal sealed class PluginService : IPluginService | ||||
|  | ||||
|         { | ||||
|             string cacheKey = $"{nameof(PluginService)}_{nameof(GetVariablePropertyTypes)}_{CultureInfo.CurrentUICulture.Name}"; | ||||
|             var dispose = businessBase == null; | ||||
|             businessBase ??= (BusinessBase)GetDriver(pluginName); // 如果 driver 为 null, 获取驱动实例 | ||||
|  | ||||
|             var data = App.CacheService.HashGetAll<List<IEditorItem>>(cacheKey); | ||||
| @@ -309,8 +300,6 @@ internal sealed class PluginService : IPluginService | ||||
|                 // 将结果存入缓存中,键为插件名称 | ||||
|                 App.CacheService.HashAdd(cacheKey, pluginName, editorItems); | ||||
|                 // 如果 dispose 参数为 true,则释放 driver 对象 | ||||
|                 if (dispose) | ||||
|                     businessBase.SafeDispose(); | ||||
|                 return editorItems; | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -54,7 +54,7 @@ public static class PluginServiceUtil | ||||
|                 { | ||||
|                     { "title", classAttribute.Remark } | ||||
|                 }; | ||||
|                 tc.ComponentParameters.AddItem( | ||||
|                 tc.ComponentParameters = tc.ComponentParameters.AddItem( | ||||
|                    new("title", classAttribute.Remark) | ||||
|                ); | ||||
|             } | ||||
|   | ||||
| @@ -47,7 +47,7 @@ public class TimeIntervalTriggerNode : TextNode, ITriggerNode, IDisposable | ||||
|     public void Dispose() | ||||
|     { | ||||
|         _task?.Stop(); | ||||
|         _task.TryDispose(); | ||||
|         _task?.TryDispose(); | ||||
|  | ||||
|         GC.SuppressFinalize(this); | ||||
|     } | ||||
|   | ||||
| @@ -468,7 +468,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService | ||||
|             if (deviceDicts.TryGetValue(a.Key, out var device) && channelDicts.TryGetValue(device.ChannelId, out var channel)) | ||||
|             { | ||||
|                 var pluginKey = channel?.PluginName; | ||||
|                 using var businessBase = (BusinessBase)GlobalData.PluginService.GetDriver(pluginKey); | ||||
|                 var businessBase = (BusinessBase)GlobalData.PluginService.GetDriver(pluginKey); | ||||
|                 return new KeyValuePair<string, VariablePropertyBase>(pluginKey, businessBase.VariablePropertys); | ||||
|             } | ||||
|             return new KeyValuePair<string, VariablePropertyBase>(string.Empty, null); | ||||
| @@ -500,7 +500,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService | ||||
|                 if (deviceDicts.TryGetValue(a.Key, out var device) && channelDicts.TryGetValue(device.ChannelId, out var channel)) | ||||
|                 { | ||||
|                     var pluginKey = channel?.PluginName; | ||||
|                     using var businessBase = (BusinessBase)GlobalData.PluginService.GetDriver(pluginKey); | ||||
|                     var businessBase = (BusinessBase)GlobalData.PluginService.GetDriver(pluginKey); | ||||
|                     return new KeyValuePair<string, VariablePropertyBase>(pluginKey, businessBase.VariablePropertys); | ||||
|                 } | ||||
|                 return new KeyValuePair<string, VariablePropertyBase>(string.Empty, null); | ||||
|   | ||||
| @@ -30,7 +30,7 @@ public static class VariableServiceHelpers | ||||
|             if (deviceDicts.TryGetValue(a.Key, out var device) && channelDicts.TryGetValue(device.ChannelId, out var channel)) | ||||
|             { | ||||
|                 var pluginKey = channel?.PluginName; | ||||
|                 using var businessBase = (BusinessBase)GlobalData.PluginService.GetDriver(pluginKey); | ||||
|                 var businessBase = (BusinessBase)GlobalData.PluginService.GetDriver(pluginKey); | ||||
|                 return new KeyValuePair<string, VariablePropertyBase>(pluginKey, businessBase.VariablePropertys); | ||||
|             } | ||||
|             return new KeyValuePair<string, VariablePropertyBase>(string.Empty, null); | ||||
|   | ||||
| @@ -9,6 +9,7 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Foundation.Dlt645; | ||||
| #pragma warning disable CA1851 | ||||
|  | ||||
| internal static class PackHelper | ||||
| { | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
| using ThingsGateway.NewLife.Extension; | ||||
|  | ||||
| namespace ThingsGateway.Foundation.Modbus; | ||||
| #pragma warning disable CA1851 | ||||
|  | ||||
| /// <summary> | ||||
| /// PackHelper | ||||
|   | ||||
| @@ -155,7 +155,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         foreach (var item in ModbusServer01ByteBlocks) | ||||
|         { | ||||
| @@ -177,7 +177,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress | ||||
|         ModbusServer02ByteBlocks.Clear(); | ||||
|         ModbusServer03ByteBlocks.Clear(); | ||||
|         ModbusServer04ByteBlocks.Clear(); | ||||
|         base.Dispose(disposing); | ||||
|         return base.DisposeAsync(disposing); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| 	<Import Project="..\..\PackNuget.props" /> | ||||
| 	<Import Project="..\..\Version.props" /> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net462;netstandard2.0;net6.0;</TargetFrameworks> | ||||
| 		<TargetFrameworks>net462;netstandard2.0;net6.0;net8.0;</TargetFrameworks> | ||||
| 		<Description>工业设备通讯协议-OpcDa协议</Description> | ||||
| 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | ||||
| 		<DocumentationFile></DocumentationFile> | ||||
|   | ||||
| @@ -28,7 +28,7 @@ public delegate void LogEventHandler(byte level, object sender, string message, | ||||
| /// <summary> | ||||
| /// OpcUaMaster | ||||
| /// </summary> | ||||
| public class OpcUaMaster : IDisposable | ||||
| public class OpcUaMaster : IDisposable, IAsyncDisposable | ||||
| { | ||||
|     #region 属性,变量等 | ||||
|  | ||||
| @@ -286,7 +286,7 @@ public class OpcUaMaster : IDisposable | ||||
|             } | ||||
|             catch (Exception) | ||||
|             { | ||||
|                 m_session.RemoveSubscription(m_subscription); | ||||
|                 await m_session.RemoveSubscriptionAsync(m_subscription, cancellationToken).ConfigureAwait(false); | ||||
|                 throw; | ||||
|             } | ||||
|         } | ||||
| @@ -312,7 +312,7 @@ public class OpcUaMaster : IDisposable | ||||
|         else if (_subscriptionDicts.TryGetValue(subscriptionName, out var existingSubscription)) | ||||
|         { | ||||
|             // remove | ||||
|             existingSubscription.Delete(true); | ||||
|             await existingSubscription.DeleteAsync(true, cancellationToken).ConfigureAwait(false); | ||||
|             await m_session.RemoveSubscriptionAsync(existingSubscription, cancellationToken).ConfigureAwait(false); | ||||
|             try { existingSubscription.Dispose(); } catch { } | ||||
|             _subscriptionDicts[subscriptionName] = m_subscription; | ||||
| @@ -405,6 +405,18 @@ public class OpcUaMaster : IDisposable | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// 断开连接。 | ||||
|     /// </summary> | ||||
|     public async Task DisconnectAsync() | ||||
|     { | ||||
|         await PrivateDisconnectAsync().ConfigureAwait(false); | ||||
|         // disconnect any existing session. | ||||
|         if (m_session != null) | ||||
|         { | ||||
|             m_session = null; | ||||
|         } | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     public void Dispose() | ||||
|     { | ||||
| @@ -412,7 +424,12 @@ public class OpcUaMaster : IDisposable | ||||
|         _variableDicts?.Clear(); | ||||
|         _subscriptionDicts?.Clear(); | ||||
|     } | ||||
|  | ||||
|     public async ValueTask DisposeAsync() | ||||
|     { | ||||
|         await DisconnectAsync().ConfigureAwait(false); | ||||
|         _variableDicts?.Clear(); | ||||
|         _subscriptionDicts?.Clear(); | ||||
|     } | ||||
|     /// <summary> | ||||
|     /// 获取变量说明 | ||||
|     /// </summary> | ||||
| @@ -715,7 +732,7 @@ public class OpcUaMaster : IDisposable | ||||
|     { | ||||
|         foreach (var item in _subscriptionDicts) | ||||
|         { | ||||
|             item.Value.Delete(true); | ||||
|             await item.Value.DeleteAsync(true).ConfigureAwait(false); | ||||
|             await m_session.RemoveSubscriptionAsync(item.Value).ConfigureAwait(false); | ||||
|             try { item.Value.Dispose(); } catch { } | ||||
|         } | ||||
| @@ -731,7 +748,7 @@ public class OpcUaMaster : IDisposable | ||||
|         if (_subscriptionDicts.TryGetValue(subscriptionName, out var subscription)) | ||||
|         { | ||||
|             // remove | ||||
|             subscription.Delete(true); | ||||
|             await subscription.DeleteAsync(true).ConfigureAwait(false); | ||||
|             await m_session.RemoveSubscriptionAsync(subscription).ConfigureAwait(false); | ||||
|             try { subscription.Dispose(); } catch { } | ||||
|             _subscriptionDicts.TryRemove(subscriptionName, out _); | ||||
| @@ -758,7 +775,7 @@ public class OpcUaMaster : IDisposable | ||||
|                 var dataValue = JsonUtils.Decode( | ||||
|                     m_session.MessageContext, | ||||
|                     variableNode.DataType, | ||||
|                     TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable), | ||||
|                    await TypeInfo.GetBuiltInTypeAsync(variableNode.DataType, m_session.SystemContext.TypeTable, cancellationToken).ConfigureAwait(false), | ||||
|                     item.Value.CalculateActualValueRank(), | ||||
|                     item.Value | ||||
|                     ); | ||||
| @@ -865,7 +882,7 @@ public class OpcUaMaster : IDisposable | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             PrivateDisconnect(); | ||||
|             await PrivateDisconnectAsync().ConfigureAwait(false); | ||||
|             if (LastServerUrl != serverUrl) | ||||
|             { | ||||
|                 _variableDicts.Clear(); | ||||
| @@ -944,6 +961,29 @@ public class OpcUaMaster : IDisposable | ||||
|             DoConnectComplete(false); | ||||
|         } | ||||
|     } | ||||
|     private async Task PrivateDisconnectAsync() | ||||
|     { | ||||
|         bool state = m_session?.Connected == true; | ||||
|  | ||||
|         if (m_reConnectHandler != null) | ||||
|         { | ||||
|             try { m_reConnectHandler.Dispose(); } catch { } | ||||
|             m_reConnectHandler = null; | ||||
|         } | ||||
|         if (m_session != null) | ||||
|         { | ||||
|             m_session.KeepAlive -= Session_KeepAlive; | ||||
|             await m_session.CloseAsync(10000).ConfigureAwait(false); | ||||
|             m_session.Dispose(); | ||||
|             m_session = null; | ||||
|         } | ||||
|  | ||||
|         if (state) | ||||
|         { | ||||
|             Log(2, null, "Disconnected"); | ||||
|             DoConnectComplete(false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #endregion 连接 | ||||
|  | ||||
| @@ -983,7 +1023,7 @@ public class OpcUaMaster : IDisposable | ||||
|         for (int i = 0; i < results.Count; i++) | ||||
|         { | ||||
|             var variableNode = await ReadNodeAsync(nodeIds[i].ToString(), false, StatusCode.IsGood(results[i].StatusCode), cancellationToken).ConfigureAwait(false); | ||||
|             var type = TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable); | ||||
|             var type = await TypeInfo.GetBuiltInTypeAsync(variableNode.DataType, m_session.SystemContext.TypeTable, cancellationToken).ConfigureAwait(false); | ||||
|             var jToken = JsonUtils.Encode(m_session.MessageContext, type, results[i].Value); | ||||
|             jTokens.Add((variableNode.NodeId.ToString(), results[i], jToken)); | ||||
|         } | ||||
| @@ -1078,7 +1118,7 @@ public class OpcUaMaster : IDisposable | ||||
|         var responseHeader = readResponse.ResponseHeader; | ||||
|         VariableNode variableNode = GetVariableNodes(itemsToRead, values, diagnosticInfos, responseHeader).FirstOrDefault(); | ||||
|  | ||||
|         if (OpcUaProperty.LoadType && variableNode.DataType != NodeId.Null && TypeInfo.GetBuiltInType(variableNode.DataType, m_session.SystemContext.TypeTable) == BuiltInType.ExtensionObject) | ||||
|         if (OpcUaProperty.LoadType && variableNode.DataType != NodeId.Null && (await TypeInfo.GetBuiltInTypeAsync(variableNode.DataType, m_session.SystemContext.TypeTable, cancellationToken).ConfigureAwait(false)) == BuiltInType.ExtensionObject) | ||||
|             await typeSystem.LoadType(variableNode.DataType, ct: cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|         if (cache) | ||||
| @@ -1178,7 +1218,7 @@ public class OpcUaMaster : IDisposable | ||||
|                 { | ||||
|                     if (cache) | ||||
|                         _variableDicts.AddOrUpdate(nodeIdStrs[i], a => node, (a, b) => node); | ||||
|                     if (node.DataType != NodeId.Null && TypeInfo.GetBuiltInType(node.DataType, m_session.SystemContext.TypeTable) == BuiltInType.ExtensionObject) | ||||
|                     if (node.DataType != NodeId.Null && (await TypeInfo.GetBuiltInTypeAsync(node.DataType, m_session.SystemContext.TypeTable, cancellationToken).ConfigureAwait(false)) == BuiltInType.ExtensionObject) | ||||
|                     { | ||||
|                         await typeSystem.LoadType(node.DataType, ct: cancellationToken).ConfigureAwait(false); | ||||
|                     } | ||||
| @@ -1376,5 +1416,7 @@ public class OpcUaMaster : IDisposable | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     #endregion 私有方法 | ||||
| } | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| 	<Import Project="..\..\PackNuget.props" /> | ||||
| 	<Import Project="..\..\Version.props" /> | ||||
| 	<PropertyGroup> | ||||
| 		<TargetFrameworks>net48;netstandard2.1;net6.0;</TargetFrameworks> | ||||
| 		<TargetFrameworks>net48;netstandard2.1;net8.0;</TargetFrameworks> | ||||
| 		<Description>工业设备通讯协议-OpcUa协议</Description> | ||||
| 		<GenerateDocumentationFile>True</GenerateDocumentationFile> | ||||
| 		<DocumentationFile></DocumentationFile> | ||||
| @@ -12,7 +12,7 @@ | ||||
|  | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.235" /> | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.244" /> | ||||
| 	</ItemGroup> | ||||
|  | ||||
| 	<ItemGroup> | ||||
|   | ||||
| @@ -9,6 +9,7 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace ThingsGateway.Foundation.SiemensS7; | ||||
| #pragma warning disable CA1851 | ||||
|  | ||||
| internal static class PackHelper | ||||
| { | ||||
|   | ||||
| @@ -417,13 +417,15 @@ public partial class SiemensS7Master : DeviceBase | ||||
|                 //本地TSAP | ||||
|                 if (SiemensS7Type == SiemensTypeEnum.S200 || SiemensS7Type == SiemensTypeEnum.S200Smart) | ||||
|                 { | ||||
|                     ISO_CR[13] = BitConverter.GetBytes(LocalTSAP)[1]; | ||||
|                     ISO_CR[14] = BitConverter.GetBytes(LocalTSAP)[0]; | ||||
|                     var data = s7BitConverter.GetBytes(LocalTSAP); | ||||
|                     ISO_CR[13] = data[0]; | ||||
|                     ISO_CR[14] = data[1]; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     ISO_CR[16] = BitConverter.GetBytes(LocalTSAP)[1]; | ||||
|                     ISO_CR[17] = BitConverter.GetBytes(LocalTSAP)[0]; | ||||
|                     var data = s7BitConverter.GetBytes(LocalTSAP); | ||||
|                     ISO_CR[16] = data[0]; | ||||
|                     ISO_CR[17] = data[1]; | ||||
|                 } | ||||
|             } | ||||
|             if (Rack > 0 || Slot > 0) | ||||
|   | ||||
| @@ -49,10 +49,10 @@ public partial class QuestDBProducer : BusinessBaseWithCacheIntervalVariable, ID | ||||
|  | ||||
|     private SqlSugarClient _db; | ||||
|  | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         _db?.TryDispose(); | ||||
|         base.Dispose(disposing); | ||||
|         return base.DisposeAsync(disposing); | ||||
|     } | ||||
|  | ||||
|     protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken) | ||||
|   | ||||
| @@ -51,10 +51,10 @@ public partial class SqlDBProducer : BusinessBaseWithCacheIntervalVariable, IDBH | ||||
|  | ||||
|     protected override BusinessPropertyWithCacheInterval _businessPropertyWithCacheInterval => _driverPropertys; | ||||
|     private SqlSugarClient _db; | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         _db?.TryDispose(); | ||||
|         base.Dispose(disposing); | ||||
|         return base.DisposeAsync(disposing); | ||||
|     } | ||||
|     public async Task<SqlSugarPagedList<IDBHistoryValue>> GetDBHistoryValuePagesAsync(DBHistoryValuePageInput input) | ||||
|     { | ||||
|   | ||||
| @@ -47,10 +47,10 @@ public partial class SqlHistoryAlarm : BusinessBaseWithCacheAlarm, IDBHistoryAla | ||||
|     /// <returns></returns> | ||||
|     public override bool IsConnected() => success; | ||||
|  | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         _db?.TryDispose(); | ||||
|         base.Dispose(disposing); | ||||
|         return base.DisposeAsync(disposing); | ||||
|     } | ||||
|  | ||||
|     protected override Task ProtectedStartAsync(CancellationToken cancellationToken) | ||||
|   | ||||
| @@ -53,10 +53,10 @@ public partial class TDengineDBProducer : BusinessBaseWithCacheIntervalVariable, | ||||
|  | ||||
|     protected override BusinessPropertyWithCacheInterval _businessPropertyWithCacheInterval => _driverPropertys; | ||||
|  | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         _db?.TryDispose(); | ||||
|         base.Dispose(disposing); | ||||
|         return base.DisposeAsync(disposing); | ||||
|     } | ||||
|  | ||||
|     protected override async Task InitChannelAsync(IChannel? channel, CancellationToken cancellationToken) | ||||
|   | ||||
| @@ -10,8 +10,6 @@ | ||||
|  | ||||
| using ThingsGateway.Gateway.Application; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Plugin.Dlt645; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -52,7 +50,8 @@ public class Dlt645_2007Master : CollectFoundationBase | ||||
|  | ||||
|         var plc = _plc; | ||||
|         _plc = new(); | ||||
|         plc?.SafeDispose(); | ||||
|         if (plc != null) | ||||
|             await plc.SafeDisposeAsync().ConfigureAwait(false); | ||||
|  | ||||
|         //载入配置 | ||||
|         _plc.DtuId = _driverPropertys.DtuId; | ||||
|   | ||||
| @@ -73,7 +73,7 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScriptAll | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
| @@ -83,7 +83,7 @@ public partial class KafkaProducer : BusinessBaseWithCacheIntervalScriptAll | ||||
|         { | ||||
|         } | ||||
|         _producer?.SafeDispose(); | ||||
|         base.Dispose(disposing); | ||||
|         return base.DisposeAsync(disposing); | ||||
|     } | ||||
|  | ||||
|     protected override Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
|     "IsWriteMemory": "IsWriteMemory", | ||||
|     "ModbusType": "ModbusType", | ||||
|     "MulStation": "MultipleStations", | ||||
|     "SendDelayTime": "SendDelayTime", | ||||
|     "Station": "DefaultStation" | ||||
|   }, | ||||
|   "ThingsGateway.Plugin.Modbus.ModbusSlaveVariableProperty": { | ||||
|   | ||||
| @@ -10,6 +10,7 @@ | ||||
|     "IsWriteMemory": "立即写入内存", | ||||
|     "ModbusType": "协议类型", | ||||
|     "MulStation": "多站点", | ||||
|     "SendDelayTime": "发送延时", | ||||
|     "Station": "默认站号" | ||||
|   }, | ||||
|   "ThingsGateway.Plugin.Modbus.ModbusSlaveVariableProperty": { | ||||
|   | ||||
| @@ -11,8 +11,6 @@ | ||||
| using ThingsGateway.Debug; | ||||
| using ThingsGateway.Gateway.Application; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Plugin.Modbus; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -65,7 +63,8 @@ public class ModbusMaster : CollectFoundationBase | ||||
|         ArgumentNullException.ThrowIfNull(channel); | ||||
|         var plc = _plc; | ||||
|         _plc = new(); | ||||
|         plc?.SafeDispose(); | ||||
|         if (plc != null) | ||||
|             await plc.SafeDisposeAsync().ConfigureAwait(false); | ||||
|         //载入配置 | ||||
|         _plc.DataFormat = _driverPropertys.DataFormat; | ||||
|         _plc.DtuId = _driverPropertys.DtuId; | ||||
|   | ||||
| @@ -92,7 +92,8 @@ public class ModbusSlave : BusinessBase | ||||
|         ArgumentNullException.ThrowIfNull(channel); | ||||
|         var plc = _plc; | ||||
|         _plc = new(); | ||||
|         plc?.SafeDispose(); | ||||
|         if (plc != null) | ||||
|             await plc.SafeDisposeAsync().ConfigureAwait(false); | ||||
|         //载入配置 | ||||
|         _plc.DataFormat = _driverPropertys.DataFormat; | ||||
|         _plc.IsStringReverseByteWord = _driverPropertys.IsStringReverseByteWord; | ||||
| @@ -100,6 +101,7 @@ public class ModbusSlave : BusinessBase | ||||
|         _plc.IsWriteMemory = _driverPropertys.IsWriteMemory; | ||||
|         _plc.MulStation = _driverPropertys.MulStation; | ||||
|         _plc.ModbusType = _driverPropertys.ModbusType; | ||||
|         _plc.SendDelayTime = _driverPropertys.SendDelayTime; | ||||
|         _plc.InitChannel(channel, LogMessage); | ||||
|         await base.InitChannelAsync(channel, cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
| @@ -136,13 +138,14 @@ public class ModbusSlave : BusinessBase | ||||
|         ); | ||||
|     } | ||||
|     /// <inheritdoc/> | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override async Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         ModbusVariables?.Clear(); | ||||
|         ModbusVariableQueue?.Clear(); | ||||
|         GlobalData.VariableValueChangeEvent -= VariableValueChange; | ||||
|         _plc?.SafeDispose(); | ||||
|         base.Dispose(disposing); | ||||
|         if (_plc != null) | ||||
|             await _plc.SafeDisposeAsync().ConfigureAwait(false); | ||||
|         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     protected override async Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) | ||||
|   | ||||
| @@ -53,4 +53,7 @@ public class ModbusSlaveProperty : BusinessPropertyBase | ||||
|     /// </summary> | ||||
|     [DynamicProperty] | ||||
|     public bool IsWriteMemory { get; set; } = true; | ||||
|  | ||||
|     [DynamicProperty] | ||||
|     public int SendDelayTime { get; set; } | ||||
| } | ||||
|   | ||||
| @@ -150,17 +150,17 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScriptAll | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override async Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         _ = Task.Run(async () => | ||||
|         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||
|  | ||||
|         if (_mqttClient != null) | ||||
|         { | ||||
|             if (_mqttClient != null) | ||||
|             { | ||||
|                 await _mqttClient.DisconnectAsync().ConfigureAwait(false); | ||||
|                 _mqttClient.SafeDispose(); | ||||
|             } | ||||
|         }); | ||||
|         base.Dispose(disposing); | ||||
|             await _mqttClient.DisconnectAsync().ConfigureAwait(false); | ||||
|             _mqttClient.SafeDispose(); | ||||
|         } | ||||
|         _mqttClient = null; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     protected override async Task ProtectedStartAsync(CancellationToken cancellationToken) | ||||
|   | ||||
| @@ -505,6 +505,8 @@ public partial class MqttClient : BusinessBaseWithCacheIntervalScriptAll | ||||
|  | ||||
|     private async ValueTask<OperResult> TryMqttClientAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         if (DisposedValue || _mqttClient == null) return new OperResult("MqttClient is disposed"); | ||||
|  | ||||
|         if (_mqttClient?.IsConnected == true) | ||||
|             return OperResult.Success; | ||||
|         return await Client().ConfigureAwait(false); | ||||
|   | ||||
| @@ -38,11 +38,16 @@ public partial class MqttCollect : CollectBase | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override async Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         _mqttClient?.SafeDispose(); | ||||
|         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||
|         if (_mqttClient != null) | ||||
|         { | ||||
|             await _mqttClient.DisconnectAsync().ConfigureAwait(false); | ||||
|             _mqttClient.SafeDispose(); | ||||
|         } | ||||
|         _mqttClient = null; | ||||
|         TopicItemDict?.Clear(); | ||||
|         base.Dispose(disposing); | ||||
|     } | ||||
|  | ||||
|     public override string GetAddressDescription() | ||||
|   | ||||
| @@ -66,9 +66,9 @@ public partial class MqttServer : BusinessBaseWithCacheIntervalScriptAll | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override async Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         base.Dispose(disposing); | ||||
|         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||
|         if (_mqttServer != null) | ||||
|         { | ||||
|             _mqttServer.ClientDisconnectedAsync -= MqttServer_ClientDisconnectedAsync; | ||||
|   | ||||
| @@ -84,14 +84,14 @@ public class OpcDaMaster : CollectBase | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     /// <inheritdoc/> | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         if (_plc != null) | ||||
|             _plc.DataChangedHandler -= DataChangedHandler; | ||||
|         _plc?.SafeDispose(); | ||||
|  | ||||
|         VariableAddresDicts?.Clear(); | ||||
|         base.Dispose(disposing); | ||||
|         return base.DisposeAsync(disposing); | ||||
|     } | ||||
|  | ||||
|     public override string GetAddressDescription() | ||||
| @@ -154,7 +154,7 @@ public class OpcDaMaster : CollectBase | ||||
|     /// <inheritdoc/> | ||||
|     protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||
|     { | ||||
|         using var writeLock = ReadWriteLock.WriterLock(); | ||||
|         using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); | ||||
|         await ValueTask.CompletedTask.ConfigureAwait(false); | ||||
|         var result = _plc.WriteItem(writeInfoLists.ToDictionary(a => a.Key.RegisterAddress!, a => a.Value.GetObjectFromJToken()!)); | ||||
|         var results = new ConcurrentDictionary<string, OperResult>(result.ToDictionary<KeyValuePair<string, Tuple<bool, string>>, string, OperResult>(a => writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key).Name, a => | ||||
|   | ||||
| @@ -70,7 +70,7 @@ public class OpcUaMaster : CollectBase | ||||
|         { | ||||
|             plc.DataChangedHandler -= DataChangedHandler; | ||||
|             plc.LogEvent -= _plc_LogEvent; | ||||
|             plc.SafeDispose(); | ||||
|             await plc.SafeDisposeAsync().ConfigureAwait(false); | ||||
|         } | ||||
|  | ||||
|         _plc.LogEvent += _plc_LogEvent; | ||||
| @@ -88,19 +88,18 @@ public class OpcUaMaster : CollectBase | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override async Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         if (_plc != null) | ||||
|         { | ||||
|             _plc.DataChangedHandler -= DataChangedHandler; | ||||
|             _plc.LogEvent -= _plc_LogEvent; | ||||
|  | ||||
|             _plc.Disconnect(); | ||||
|             _plc.SafeDispose(); | ||||
|             await _plc.DisposeAsync().ConfigureAwait(false); | ||||
|         } | ||||
|  | ||||
|         VariableAddresDicts?.Clear(); | ||||
|         base.Dispose(disposing); | ||||
|         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     public override string GetAddressDescription() | ||||
| @@ -275,7 +274,7 @@ public class OpcUaMaster : CollectBase | ||||
|     /// <inheritdoc/> | ||||
|     protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||
|     { | ||||
|         using var writeLock = ReadWriteLock.WriterLock(); | ||||
|         using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); | ||||
|         var result = await _plc.WriteNodeAsync(writeInfoLists.ToDictionary(a => a.Key.RegisterAddress!, a => a.Value), cancellationToken).ConfigureAwait(false); | ||||
|         var results = new ConcurrentDictionary<string, OperResult>(result.ToDictionary<KeyValuePair<string, Tuple<bool, string>>, string, OperResult>(a => writeInfoLists.Keys.FirstOrDefault(b => b.RegisterAddress == a.Key)?.Name! | ||||
|         , a => | ||||
| @@ -301,7 +300,8 @@ public class OpcUaMaster : CollectBase | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             _plc?.Disconnect(); | ||||
|             if (_plc != null) | ||||
|                 await _plc.DisconnectAsync().ConfigureAwait(false); | ||||
|             await base.AfterVariablesChangedAsync(cancellationToken).ConfigureAwait(false); | ||||
|         } | ||||
|         finally | ||||
|   | ||||
| @@ -175,13 +175,13 @@ public partial class OpcUaServer : BusinessBase | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         GlobalData.VariableValueChangeEvent -= VariableValueChange; | ||||
|         UaDispose(); | ||||
|         CollectVariableRuntimes?.Clear(); | ||||
|         IdVariableRuntimes?.Clear(); | ||||
|         base.Dispose(disposing); | ||||
|         return base.DisposeAsync(disposing); | ||||
|     } | ||||
|  | ||||
|     protected override async Task ProtectedStartAsync(CancellationToken cancellationToken) | ||||
|   | ||||
| @@ -119,7 +119,7 @@ public partial class OpcUaMaster : IDisposable | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             _plc.Disconnect(); | ||||
|             await _plc.DisconnectAsync().ConfigureAwait(false); | ||||
|             LogPath = _plc?.GetHashCode().ToLong().GetDebugLogPath(); | ||||
|             await GetOpc().ConnectAsync(CancellationToken.None); | ||||
|         } | ||||
|   | ||||
| @@ -22,27 +22,27 @@ | ||||
| 		</ProjectReference> | ||||
| 		<ProjectReference Include="..\ThingsGateway.Foundation.OpcUa\ThingsGateway.Foundation.OpcUa.csproj" /> | ||||
|  | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.376.235" GeneratePathProperty="true"> | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.376.244" GeneratePathProperty="true"> | ||||
| 			<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets> | ||||
| 		</PackageReference> | ||||
|  | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.235" GeneratePathProperty="true"> | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes" Version="1.5.376.244" GeneratePathProperty="true"> | ||||
| 			<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets> | ||||
| 		</PackageReference> | ||||
|  | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.376.235" GeneratePathProperty="true"> | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Client" Version="1.5.376.244" GeneratePathProperty="true"> | ||||
| 			<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets> | ||||
| 		</PackageReference> | ||||
|  | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Core" Version="1.5.376.235" GeneratePathProperty="true"> | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Core" Version="1.5.376.244" GeneratePathProperty="true"> | ||||
| 			<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets> | ||||
| 		</PackageReference> | ||||
|  | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Configuration" Version="1.5.376.235" GeneratePathProperty="true"> | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Configuration" Version="1.5.376.244" GeneratePathProperty="true"> | ||||
| 			<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets> | ||||
| 		</PackageReference> | ||||
|  | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Security.Certificates" Version="1.5.376.235" GeneratePathProperty="true"> | ||||
| 		<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Security.Certificates" Version="1.5.376.244" GeneratePathProperty="true"> | ||||
| 			<PrivateAssets>contentFiles;compile;build;buildMultitargeting;buildTransitive;analyzers;</PrivateAssets> | ||||
| 		</PackageReference> | ||||
|  | ||||
|   | ||||
| @@ -12,8 +12,6 @@ using RabbitMQ.Client; | ||||
|  | ||||
| using ThingsGateway.Foundation; | ||||
|  | ||||
| using TouchSocket.Core; | ||||
|  | ||||
| namespace ThingsGateway.Plugin.RabbitMQ; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -53,11 +51,13 @@ public partial class RabbitMQProducer : BusinessBaseWithCacheIntervalScriptAll | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     protected override void Dispose(bool disposing) | ||||
|     protected override async Task DisposeAsync(bool disposing) | ||||
|     { | ||||
|         _channel?.SafeDispose(); | ||||
|         _connection?.SafeDispose(); | ||||
|         base.Dispose(disposing); | ||||
|         if (_channel != null) | ||||
|             await _channel.SafeDisposeAsync().ConfigureAwait(false); | ||||
|         if (_connection != null) | ||||
|             await _connection.SafeDisposeAsync().ConfigureAwait(false); | ||||
|         await base.DisposeAsync(disposing).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     protected override async Task ProtectedExecuteAsync(object? state, CancellationToken cancellationToken) | ||||
|   | ||||
| @@ -60,7 +60,8 @@ public class SiemensS7Master : CollectFoundationBase | ||||
|  | ||||
|         var plc = _plc; | ||||
|         _plc = new(); | ||||
|         plc?.SafeDispose(); | ||||
|         if (plc != null) | ||||
|             await plc.SafeDisposeAsync().ConfigureAwait(false); | ||||
|  | ||||
|         //载入配置 | ||||
|         _plc.DataFormat = _driverPropertys.DataFormat; | ||||
| @@ -95,7 +96,7 @@ public class SiemensS7Master : CollectFoundationBase | ||||
|  | ||||
|     protected override async ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||
|     { | ||||
|         using var writeLock = ReadWriteLock.WriterLock(); | ||||
|         using var writeLock = await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); | ||||
|  | ||||
|         // 检查协议是否为空,如果为空则抛出异常 | ||||
|         if (FoundationDevice == null) | ||||
|   | ||||
| @@ -215,7 +215,7 @@ | ||||
| //    } | ||||
|  | ||||
| //    /// <summary> | ||||
| //    /// 写入变量,实现设备写入操作,注意执行写锁,using var writeLock = ReadWriteLock.WriterLock(); | ||||
| //    /// 写入变量,实现设备写入操作,注意执行写锁,        using var writeLock =await ReadWriteLock.WriterLockAsync(cancellationToken).ConfigureAwait(false); | ||||
| //    /// </summary> | ||||
| //    protected override ValueTask<Dictionary<string, OperResult>> WriteValuesAsync(Dictionary<VariableRuntime, JToken> writeInfoLists, CancellationToken cancellationToken) | ||||
| //    { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user