mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-26 13:25:18 +08:00
Compare commits
10 Commits
10.11.116.
...
10.12.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96b4287f3a | ||
|
|
7d406de29f | ||
|
|
81f0ef466a | ||
|
|
3f2d6b133c | ||
|
|
e776dc67eb | ||
|
|
bc5827d140 | ||
|
|
21838bf4af | ||
|
|
6090108597 | ||
|
|
b47b9e6f43 | ||
|
|
18d1cffb2d |
@@ -19,12 +19,14 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0' ">
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="8.0.2" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="9.0.10" />
|
||||
<PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
@@ -92,7 +92,8 @@ public class Startup : AppStartup
|
||||
options.RootComponents.MaxJSRootComponents = 500;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 20;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
|
||||
options.DisconnectedCircuitMaxRetained = 1;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
|
||||
})
|
||||
.AddHubOptions(options =>
|
||||
{
|
||||
@@ -103,6 +104,7 @@ public class Startup : AppStartup
|
||||
options.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
|
||||
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
|
||||
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
});
|
||||
|
||||
#else
|
||||
@@ -112,7 +114,8 @@ public class Startup : AppStartup
|
||||
options.RootComponents.MaxJSRootComponents = 500;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 20;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
|
||||
options.DisconnectedCircuitMaxRetained = 1;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
|
||||
}).AddHubOptions(options =>
|
||||
{
|
||||
//单个传入集线器消息的最大大小。默认 32 KB
|
||||
|
||||
@@ -15,15 +15,11 @@
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon>
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
<CETCompat>false</CETCompat>
|
||||
<!--使用自托管线程池-->
|
||||
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
|
||||
|
||||
<!--<PlatformTarget>x86</PlatformTarget>-->
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ internal class CacheManager
|
||||
{
|
||||
private IMemoryCache Cache { get; set; }
|
||||
|
||||
private IServiceProvider Provider { get; set; }
|
||||
private static IServiceProvider Provider => App.RootServices;
|
||||
|
||||
[NotNull]
|
||||
private static CacheManager? Instance { get; set; }
|
||||
@@ -40,8 +40,7 @@ internal class CacheManager
|
||||
static CacheManager()
|
||||
{
|
||||
Instance = new();
|
||||
Instance.Provider = App.RootServices;
|
||||
Instance.Cache = Instance.Provider.GetRequiredService<IMemoryCache>();
|
||||
Instance.Cache = Provider.GetRequiredService<IMemoryCache>();
|
||||
Options = App.RootServices.GetRequiredService<IOptions<BootstrapBlazorOptions>>().Value;
|
||||
}
|
||||
|
||||
@@ -236,7 +235,7 @@ internal class CacheManager
|
||||
/// <returns></returns>
|
||||
public static IStringLocalizer? CreateLocalizerByType(Type resourceSource) => resourceSource.Assembly.IsDynamic
|
||||
? null
|
||||
: Instance.Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource);
|
||||
: Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 <see cref="JsonLocalizationOptions"/> 值
|
||||
@@ -244,7 +243,7 @@ internal class CacheManager
|
||||
/// <returns></returns>
|
||||
private static JsonLocalizationOptions GetJsonLocalizationOption()
|
||||
{
|
||||
var localizationOptions = Instance.Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>();
|
||||
var localizationOptions = Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>();
|
||||
return localizationOptions.Value;
|
||||
}
|
||||
/// <summary>
|
||||
@@ -253,7 +252,7 @@ internal class CacheManager
|
||||
/// <returns></returns>
|
||||
private static BootstrapBlazorOptions GetBootstrapBlazorOption()
|
||||
{
|
||||
var localizationOptions = Instance.Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>();
|
||||
var localizationOptions = Provider.GetRequiredService<IOptions<BootstrapBlazorOptions>>();
|
||||
return localizationOptions.Value;
|
||||
}
|
||||
/// <summary>
|
||||
@@ -269,7 +268,7 @@ internal class CacheManager
|
||||
return null;
|
||||
}
|
||||
IStringLocalizer? ret = null;
|
||||
var factories = Instance.Provider.GetServices<IStringLocalizerFactory>();
|
||||
var factories = Provider.GetServices<IStringLocalizerFactory>();
|
||||
var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory);
|
||||
if (factory != null)
|
||||
{
|
||||
@@ -345,7 +344,7 @@ internal class CacheManager
|
||||
/// <param name="typeName"></param>
|
||||
/// <param name="includeParentCultures"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Instance.Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures);
|
||||
public static IEnumerable<LocalizedString> GetTypeStringsFromResolve(string typeName, bool includeParentCultures = true) => Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByType(typeName, includeParentCultures);
|
||||
#endregion
|
||||
|
||||
#region DisplayName
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.7" />
|
||||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.11.2" />
|
||||
<PackageReference Include="BootstrapBlazor" Version="9.11.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -25,17 +25,11 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
|
||||
/// <summary>最大个数。默认0,0表示无上限</summary>
|
||||
public Int32 Max { get; set; } = 0;
|
||||
|
||||
/// <summary>最小个数。默认1</summary>
|
||||
public Int32 Min { get; set; } = 1;
|
||||
|
||||
private readonly object _syncRoot = new();
|
||||
|
||||
/// <summary>基础空闲集合。只保存最小个数,最热部分</summary>
|
||||
private readonly Stack<T> _free = new();
|
||||
|
||||
/// <summary>扩展空闲集合。保存最小个数以外部分</summary>
|
||||
private readonly Queue<T> _free2 = new();
|
||||
|
||||
/// <summary>借出去的放在这</summary>
|
||||
private readonly HashSet<T> _busy = new();
|
||||
|
||||
@@ -79,7 +73,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
|
||||
if (_inited) return;
|
||||
_inited = true;
|
||||
|
||||
WriteLog($"Init {typeof(T).FullName} Min={Min} Max={Max}");
|
||||
WriteLog($"Init {typeof(T).FullName} Max={Max}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -99,26 +93,20 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
|
||||
pi = _free.Pop();
|
||||
_FreeCount--;
|
||||
}
|
||||
else if (_free2.Count > 0)
|
||||
{
|
||||
pi = _free2.Dequeue();
|
||||
_FreeCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = BusyCount;
|
||||
if (Max > 0 && count >= Max)
|
||||
if (Max > 0 && BusyCount >= Max)
|
||||
{
|
||||
var msg = $"申请失败,已有 {count:n0} 达到或超过最大值 {Max:n0}";
|
||||
var msg = $"申请失败,已有 {BusyCount:n0} 达到或超过最大值 {Max:n0}";
|
||||
WriteLog("Acquire Max " + msg);
|
||||
throw new Exception(Name + " " + msg);
|
||||
}
|
||||
|
||||
pi = OnCreate();
|
||||
if (count == 0) Init();
|
||||
if (BusyCount == 0) Init();
|
||||
|
||||
#if DEBUG
|
||||
WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, count + 1);
|
||||
WriteLog("Acquire Create Free={0} Busy={1}", FreeCount, BusyCount + 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -177,10 +165,7 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
|
||||
}
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_FreeCount < Min)
|
||||
_free.Push(value);
|
||||
else
|
||||
_free2.Enqueue(value);
|
||||
_free.Push(value);
|
||||
_FreeCount++;
|
||||
}
|
||||
|
||||
@@ -214,12 +199,6 @@ public class ObjectPoolLock<T> : DisposeBase, IPool<T> where T : class
|
||||
OnDispose(pi);
|
||||
}
|
||||
|
||||
while (_free2.Count > 0)
|
||||
{
|
||||
var pi = _free2.Dequeue();
|
||||
OnDispose(pi);
|
||||
}
|
||||
|
||||
_FreeCount = 0;
|
||||
|
||||
foreach (var item in _busy)
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using ThingsGateway.NewLife.Collections;
|
||||
|
||||
namespace ThingsGateway.NewLife;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -61,7 +61,8 @@ public class TextFileLog : Logger, IDisposable
|
||||
MaxBytes = set.LogFileMaxBytes;
|
||||
Backups = set.LogFileBackups;
|
||||
|
||||
_Timer = new TimerX(DoWriteAndClose, null, 0_000, 5_000) { Async = true };
|
||||
_Timer = new TimerX(DoWriteAndClose, null, 0_000, 60_000, nameof(TextFileLog)) { Async = true };
|
||||
_WriteTimer = new TimerX(DoWrite, null, 0_000, 1000, nameof(TextFileLog)) { Async = true };
|
||||
}
|
||||
|
||||
private static readonly NonBlockingDictionary<String, TextFileLog> cache = new(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -96,6 +97,7 @@ public class TextFileLog : Logger, IDisposable
|
||||
protected virtual void Dispose(Boolean disposing)
|
||||
{
|
||||
_Timer.TryDispose();
|
||||
_WriteTimer.TryDispose();
|
||||
|
||||
// 销毁前把队列日志输出
|
||||
if (Interlocked.CompareExchange(ref _writing, 1, 0) == 0) WriteAndClose(DateTime.MinValue);
|
||||
@@ -147,35 +149,72 @@ public class TextFileLog : Logger, IDisposable
|
||||
|
||||
/// <summary>获取日志文件路径</summary>
|
||||
/// <returns></returns>
|
||||
private String? GetLogFile()
|
||||
private string? GetLogFile()
|
||||
{
|
||||
// 单日志文件
|
||||
if (_isFile) return LogPath.GetBasePath();
|
||||
|
||||
// 目录多日志文件
|
||||
var logfile = LogPath.CombinePath(String.Format(FileFormat, TimerX.Now.AddHours(Setting.Current.UtcIntervalHours), Level)).GetBasePath();
|
||||
var baseFile = LogPath.CombinePath(
|
||||
string.Format(FileFormat, TimerX.Now.AddHours(Setting.Current.UtcIntervalHours), Level)
|
||||
).GetBasePath();
|
||||
|
||||
// 是否限制文件大小
|
||||
if (MaxBytes == 0) return logfile;
|
||||
// 不限制大小
|
||||
if (MaxBytes == 0) return baseFile;
|
||||
|
||||
// 找到今天第一个未达到最大上限的文件
|
||||
var max = MaxBytes * 1024L * 1024L;
|
||||
var ext = Path.GetExtension(logfile);
|
||||
var name = logfile.TrimEnd(ext);
|
||||
var ext = Path.GetExtension(FileFormat);
|
||||
|
||||
string? latestFile = null;
|
||||
DateTime latestTime = DateTime.MinValue;
|
||||
|
||||
foreach (var path in Directory.EnumerateFiles(LogPath, $"*{ext}", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
try
|
||||
{
|
||||
var lastWrite = File.GetLastWriteTimeUtc(path);
|
||||
if (lastWrite > latestTime)
|
||||
{
|
||||
latestTime = lastWrite;
|
||||
latestFile = path;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (latestFile != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var len = new FileInfo(latestFile).Length;
|
||||
if (len < max)
|
||||
return latestFile;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
var fileNameWithoutExt = Path.Combine(
|
||||
Path.GetDirectoryName(baseFile)!,
|
||||
Path.GetFileNameWithoutExtension(baseFile)
|
||||
);
|
||||
|
||||
// 依序找下一个可用文件
|
||||
for (var i = 1; i < 1024; i++)
|
||||
{
|
||||
if (i > 1) logfile = $"{name}_{i}{ext}";
|
||||
var nextFile = i == 1 ? $"{fileNameWithoutExt}{ext}" : $"{fileNameWithoutExt}_{i}{ext}";
|
||||
if (!File.Exists(nextFile))
|
||||
return nextFile;
|
||||
|
||||
var fi = logfile.AsFile();
|
||||
if (!fi.Exists || fi.Length < max) return logfile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 异步写日志
|
||||
private readonly TimerX? _Timer;
|
||||
private readonly TimerX? _WriteTimer;
|
||||
private readonly ConcurrentQueue<String> _Logs = new();
|
||||
private volatile Int32 _logCount;
|
||||
private Int32 _writing;
|
||||
@@ -186,9 +225,9 @@ public class TextFileLog : Logger, IDisposable
|
||||
{
|
||||
var writer = LogWriter;
|
||||
|
||||
var now = TimerX.Now.AddHours(Setting.Current.UtcIntervalHours);
|
||||
var logFile = GetLogFile();
|
||||
if (logFile.IsNullOrEmpty()) return;
|
||||
var now = TimerX.Now.AddHours(Setting.Current.UtcIntervalHours);
|
||||
|
||||
if (!_isFile && logFile != CurrentLogFile)
|
||||
{
|
||||
@@ -223,7 +262,10 @@ public class TextFileLog : Logger, IDisposable
|
||||
// 连续5秒没日志,就关闭
|
||||
_NextClose = now.AddSeconds(5);
|
||||
}
|
||||
|
||||
private void DoWrite(Object? state)
|
||||
{
|
||||
WriteLog();
|
||||
}
|
||||
/// <summary>关闭文件</summary>
|
||||
private void DoWriteAndClose(Object? state)
|
||||
{
|
||||
@@ -237,43 +279,36 @@ public class TextFileLog : Logger, IDisposable
|
||||
DirectoryInfo? di = new DirectoryInfo(LogPath);
|
||||
if (di.Exists)
|
||||
{
|
||||
// 删除*.del
|
||||
// 删除 *.del
|
||||
try
|
||||
{
|
||||
var dels = di.GetFiles("*.del");
|
||||
if (dels?.Length > 0)
|
||||
foreach (var item in di.EnumerateFiles("*.del"))
|
||||
{
|
||||
foreach (var item in dels)
|
||||
{
|
||||
item.Delete();
|
||||
}
|
||||
item.Delete();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
var ext = Path.GetExtension(FileFormat);
|
||||
var fis = di.GetFiles("*" + ext);
|
||||
if (fis != null && fis.Length > Backups)
|
||||
var fis = di.EnumerateFiles($"*{ext}")
|
||||
.OrderByDescending(e => e.LastWriteTimeUtc)
|
||||
.Skip(Backups);
|
||||
|
||||
foreach (var item in fis)
|
||||
{
|
||||
// 删除最旧的文件
|
||||
var retain = fis.Length - Backups;
|
||||
fis = fis.OrderBy(e => e.LastWriteTimeUtc).Take(retain).ToArray();
|
||||
foreach (var item in fis)
|
||||
OnWrite(LogLevel.Info, "The log file has reached the maximum limit of {0}, delete {1}, size {2: n0} Byte", Backups, item.Name, item.Length);
|
||||
try
|
||||
{
|
||||
item.Delete();
|
||||
}
|
||||
catch
|
||||
{
|
||||
OnWrite(LogLevel.Info, "The log file has reached the maximum limit of {0}, delete {1}, size {2: n0} Byte", Backups, item.Name, item.Length);
|
||||
try
|
||||
{
|
||||
item.Delete();
|
||||
item.MoveTo(item.FullName + ".del");
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
item.MoveTo(item.FullName + ".del");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -323,7 +358,6 @@ public class TextFileLog : Logger, IDisposable
|
||||
// 推入队列
|
||||
Enqueue($"{e.GetAndReset()}");
|
||||
|
||||
WriteLog();
|
||||
}
|
||||
|
||||
protected bool Check()
|
||||
@@ -340,35 +374,17 @@ public class TextFileLog : Logger, IDisposable
|
||||
}
|
||||
protected void WriteLog()
|
||||
{
|
||||
// 异步写日志,实时。即使这里错误,定时器那边仍然会补上
|
||||
// 写日志,实时。即使这里错误,定时器那边仍然会补上
|
||||
if (Interlocked.CompareExchange(ref _writing, 1, 0) == 0)
|
||||
{
|
||||
// 调试级别 或 致命错误 同步写日志
|
||||
if (Setting.Current.LogLevel <= LogLevel.Debug || Level >= LogLevel.Error)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
WriteFile();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_writing = 0;
|
||||
}
|
||||
if (!_Logs.IsEmpty) WriteFile();
|
||||
}
|
||||
else
|
||||
finally
|
||||
{
|
||||
ThreadPool.UnsafeQueueUserWorkItem(s =>
|
||||
{
|
||||
try
|
||||
{
|
||||
WriteFile();
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_writing = 0;
|
||||
}
|
||||
}, null);
|
||||
_writing = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,14 @@ public static class XTrace
|
||||
|
||||
Log.Error("{0}", ex);
|
||||
}
|
||||
public static void WriteException(Exception ex, string message)
|
||||
{
|
||||
if (!InitLog()) return;
|
||||
|
||||
WriteVersion();
|
||||
|
||||
Log.Error("{0}, {1}", message, ex);
|
||||
}
|
||||
#endregion 写日志
|
||||
|
||||
#region 构造
|
||||
|
||||
@@ -12,18 +12,13 @@ namespace PooledAwait
|
||||
{
|
||||
private static ObjectPoolLock<T> pool = new();
|
||||
|
||||
[ThreadStatic]
|
||||
private static T? ts_local;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an instance from the pool if possible
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T? TryGet()
|
||||
{
|
||||
var tmp = ts_local;
|
||||
ts_local = null;
|
||||
return tmp ?? pool.Get();
|
||||
return pool.Get();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -34,11 +29,6 @@ namespace PooledAwait
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
if (ts_local == null)
|
||||
{
|
||||
ts_local = value;
|
||||
return;
|
||||
}
|
||||
pool.Return(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,21 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
public static TimeProvider GlobalTimeProvider { get; set; } = TimeProvider.System;
|
||||
#endregion
|
||||
#region 构造
|
||||
private TimerScheduler(String name) => Name = name;
|
||||
|
||||
private TimerScheduler(String name)
|
||||
{
|
||||
Name = name;
|
||||
_processCallback = state =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Execute(state);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
XTrace.WriteException(ex, "Timer执行错误");
|
||||
}
|
||||
};
|
||||
}
|
||||
/// <summary>销毁</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
@@ -258,17 +271,7 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
else
|
||||
//Task.Factory.StartNew(() => ProcessItem(timer));
|
||||
// 不需要上下文流动,捕获所有异常
|
||||
ThreadPool.UnsafeQueueUserWorkItem(s =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Execute(s);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
XTrace.WriteException(ex);
|
||||
}
|
||||
}, timer);
|
||||
ThreadPool.UnsafeQueueUserWorkItem(_processCallback, timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +286,7 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
|
||||
WriteLog("调度线程已退出:{0}", Name);
|
||||
}
|
||||
|
||||
private readonly WaitCallback _processCallback;
|
||||
/// <summary>检查定时器是否到期</summary>
|
||||
/// <param name="timer"></param>
|
||||
/// <param name="now"></param>
|
||||
@@ -325,9 +328,9 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
|
||||
timer.hasSetNext = false;
|
||||
|
||||
string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||
string timerArg = timer.Timers.ToString();
|
||||
using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||
//string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||
//string timerArg = timer.Timers.ToString();
|
||||
//using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||
var sw = ValueStopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
@@ -351,7 +354,7 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
// 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事
|
||||
catch (Exception ex)
|
||||
{
|
||||
span?.SetError(ex, null);
|
||||
//span?.SetError(ex, null);
|
||||
XTrace.WriteException(ex);
|
||||
}
|
||||
finally
|
||||
@@ -377,9 +380,9 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
|
||||
timer.hasSetNext = false;
|
||||
|
||||
string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||
string timerArg = timer.Timers.ToString();
|
||||
using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||
//string tracerName = timer.TracerName ?? "timer:ExecuteAsync";
|
||||
//string timerArg = timer.Timers.ToString();
|
||||
//using var span = timer.Tracer?.NewSpan(tracerName, timerArg);
|
||||
var sw = ValueStopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
@@ -427,7 +430,7 @@ public class TimerScheduler : IDisposable, ILogFeature
|
||||
// 如果用户代码没有拦截错误,则这里拦截,避免出错了都不知道怎么回事
|
||||
catch (Exception ex)
|
||||
{
|
||||
span?.SetError(ex, null);
|
||||
//span?.SetError(ex, null);
|
||||
XTrace.WriteException(ex);
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.2" />
|
||||
<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" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="9.0.10" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<PluginVersion>10.11.116</PluginVersion>
|
||||
<ProPluginVersion>10.11.116</ProPluginVersion>
|
||||
<DefaultVersion>10.11.116</DefaultVersion>
|
||||
<PluginVersion>10.12.4</PluginVersion>
|
||||
<ProPluginVersion>10.12.4</ProPluginVersion>
|
||||
<DefaultVersion>10.12.4</DefaultVersion>
|
||||
<AuthenticationVersion>10.11.6</AuthenticationVersion>
|
||||
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
|
||||
<NET8Version>8.0.21</NET8Version>
|
||||
|
||||
@@ -10,53 +10,53 @@
|
||||
<div class="w-100" style=@($"height:{HeightString}")>
|
||||
|
||||
<Card HeaderText=@HeaderText class=@("w-100 h-100")>
|
||||
<HeaderTemplate>
|
||||
<div class="flex-fill">
|
||||
</div>
|
||||
<HeaderTemplate>
|
||||
<div class="flex-fill">
|
||||
</div>
|
||||
|
||||
@if (LogLevelChanged.HasDelegate)
|
||||
{
|
||||
<Select Value="@LogLevel" ValueChanged="LogLevelChanged" IsPopover></Select>
|
||||
}
|
||||
<Tooltip class=" col-auto" Title="@RazorLocalizer[Pause?"Play":"Pause"]" Placement="Placement.Bottom">
|
||||
@if (LogLevelChanged.HasDelegate)
|
||||
{
|
||||
<Select Value="@LogLevel" ValueChanged="LogLevelChanged" IsPopover></Select>
|
||||
}
|
||||
<Tooltip class=" col-auto" Title=@(Pause? PlayText:PauseText) Placement="Placement.Bottom">
|
||||
|
||||
<Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(Pause?"fa fa-play":"fa fa-pause") OnClick="OnPause" />
|
||||
<Button Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@(Pause ? "fa fa-play" : "fa fa-pause") OnClick="OnPause" />
|
||||
|
||||
</Tooltip>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip class=" col-auto" Title="@RazorLocalizer["Export"]" Placement="Placement.Bottom">
|
||||
<Tooltip class=" col-auto" Title="@ExportText" Placement="Placement.Bottom">
|
||||
|
||||
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" />
|
||||
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("fa fa-sign-out") OnClick="HandleOnExportClick" />
|
||||
|
||||
</Tooltip>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip class=" col-auto" Title="@RazorLocalizer["Delete"]" Placement="Placement.Bottom">
|
||||
<Tooltip class=" col-auto" Title="@DeleteText" Placement="Placement.Bottom">
|
||||
|
||||
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" />
|
||||
<Button IsAsync Color="Color.None" style="color: var(--bs-card-title-color);" Icon=@("far fa-trash-alt") OnClick="Delete" />
|
||||
|
||||
</Tooltip>
|
||||
</Tooltip>
|
||||
|
||||
|
||||
</HeaderTemplate>
|
||||
<BodyTemplate>
|
||||
<div style=@($"height:calc(100% - 50px);overflow-y:scroll;flex-fill;")>
|
||||
<Virtualize Items="CurrentMessages??new List<LogMessage>()" Context="itemMessage" ItemSize="60" OverscanCount=2>
|
||||
<ItemContent>
|
||||
@* <Tooltip Placement="Placement.Bottom" Title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> *@
|
||||
<div class=@(itemMessage.Level<(byte)Microsoft.Extensions.Logging.LogLevel.Information?"":
|
||||
itemMessage.Level>=(byte)Microsoft.Extensions.Logging.LogLevel.Warning? " red--text text-truncate":"green--text text-truncate")
|
||||
title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))>
|
||||
</HeaderTemplate>
|
||||
<BodyTemplate>
|
||||
<div style=@($"height:calc(100% - 50px);overflow-y:scroll;flex-fill;")>
|
||||
<Virtualize Items="CurrentMessages ?? new List<LogMessage>()" Context="itemMessage" ItemSize="60" OverscanCount=2>
|
||||
<ItemContent>
|
||||
@* <Tooltip Placement="Placement.Bottom" Title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))> *@
|
||||
<div class=@(itemMessage.Level<(byte)Microsoft.Extensions.Logging.LogLevel.Information?"":
|
||||
itemMessage.Level >= (byte)Microsoft.Extensions.Logging.LogLevel.Warning ? " red--text text-truncate" : "green--text text-truncate")
|
||||
title=@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 500))>
|
||||
|
||||
@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 150))
|
||||
@itemMessage.Message.Substring(0, Math.Min(itemMessage.Message.Length, 150))
|
||||
|
||||
</div>
|
||||
@* </Tooltip> *@
|
||||
</ItemContent>
|
||||
</Virtualize>
|
||||
</div>
|
||||
</div>
|
||||
@* </Tooltip> *@
|
||||
</ItemContent>
|
||||
</Virtualize>
|
||||
</div>
|
||||
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
</BodyTemplate>
|
||||
</Card>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -10,12 +10,11 @@
|
||||
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using ThingsGateway.Extension;
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.NewLife;
|
||||
using ThingsGateway.NewLife.Threading;
|
||||
|
||||
using TouchSocket.Core;
|
||||
|
||||
@@ -23,6 +22,24 @@ namespace ThingsGateway.Debug;
|
||||
|
||||
public partial class LogConsole : IDisposable
|
||||
{
|
||||
|
||||
private string PlayText { get; set; }
|
||||
private string PauseText { get; set; }
|
||||
private string ExportText { get; set; }
|
||||
private string DeleteText { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
PlayText = RazorLocalizer["Play"];
|
||||
PauseText = RazorLocalizer["Pause"];
|
||||
ExportText = RazorLocalizer["Export"];
|
||||
DeleteText = RazorLocalizer["Delete"];
|
||||
|
||||
_Timer = new TimerX(RunTimerAsync, null, 1_000, 1_000, nameof(LogConsole)) { Async = true };
|
||||
base.OnInitialized();
|
||||
}
|
||||
private TimerX _Timer;
|
||||
|
||||
private bool Pause;
|
||||
|
||||
public bool Disposed { get; set; }
|
||||
@@ -69,7 +86,7 @@ public partial class LogConsole : IDisposable
|
||||
{
|
||||
logPath = LogPath;
|
||||
Messages = new List<LogMessage>();
|
||||
await ExecuteAsync();
|
||||
_Timer?.SetNext(0);
|
||||
}
|
||||
|
||||
await base.OnParametersSetAsync();
|
||||
@@ -82,63 +99,38 @@ public partial class LogConsole : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
_Timer?.SafeDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
private WaitLock WaitLock = new(nameof(LogConsole));
|
||||
protected async Task ExecuteAsync()
|
||||
protected async ValueTask ExecuteAsync()
|
||||
{
|
||||
if (WaitLock.Waited) return;
|
||||
try
|
||||
{
|
||||
await WaitLock.WaitAsync();
|
||||
await Task.Delay(1000);
|
||||
|
||||
if (LogPath != null)
|
||||
if (LogPath != null)
|
||||
{
|
||||
var files = await TextFileReadService.GetLogFilesAsync(LogPath);
|
||||
if (!files.IsSuccess)
|
||||
{
|
||||
var files = await TextFileReadService.GetLogFilesAsync(LogPath);
|
||||
if (!files.IsSuccess)
|
||||
Messages = new List<LogMessage>();
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault());
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
Messages = new List<LogMessage>();
|
||||
await Task.Delay(1000);
|
||||
Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
var result = await TextFileReadService.LastLogDataAsync(files.Content.FirstOrDefault());
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
Messages = result.Content.Where(a => a.LogLevel >= LogLevel).Select(a => new LogMessage((int)a.LogLevel, $"{a.LogTime} - {a.Message}{(a.ExceptionString.IsNullOrWhiteSpace() ? null : $"{Environment.NewLine}{a.ExceptionString}")}")).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages = new List<LogMessage>();
|
||||
}
|
||||
sw.Stop();
|
||||
if (sw.ElapsedMilliseconds > 500)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
});
|
||||
Messages = Array.Empty<LogMessage>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NewLife.Log.XTrace.WriteException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
WaitLock.Release();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_ = RunTimerAsync();
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
private async Task Delete()
|
||||
{
|
||||
@@ -185,19 +177,9 @@ public partial class LogConsole : IDisposable
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task RunTimerAsync()
|
||||
private async Task RunTimerAsync(object? state)
|
||||
{
|
||||
while (!Disposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ExecuteAsync();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NewLife.Log.XTrace.WriteException(ex);
|
||||
}
|
||||
}
|
||||
await ExecuteAsync();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using PooledAwait;
|
||||
|
||||
using ThingsGateway.Foundation.Extension.String;
|
||||
|
||||
using TouchSocket.SerialPorts;
|
||||
@@ -26,65 +28,33 @@ public static class ChannelOptionsExtensions
|
||||
/// <param name="e">接收数据</param>
|
||||
/// <param name="funcs">事件</param>
|
||||
/// <returns></returns>
|
||||
internal static ValueTask OnChannelReceivedEvent(
|
||||
this IClientChannel clientChannel,
|
||||
ReceivedDataEventArgs e,
|
||||
ChannelReceivedEventHandler funcs)
|
||||
internal static ValueTask OnChannelReceivedEvent(this IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||
{
|
||||
clientChannel.ThrowIfNull(nameof(IClientChannel));
|
||||
e.ThrowIfNull(nameof(ReceivedDataEventArgs));
|
||||
funcs.ThrowIfNull(nameof(ChannelReceivedEventHandler));
|
||||
|
||||
if (funcs.Count == 0) return EasyValueTask.CompletedTask;
|
||||
return OnChannelReceivedEvent(clientChannel, e, funcs);
|
||||
|
||||
return InvokeHandlersSequentially(clientChannel, e, funcs);
|
||||
}
|
||||
|
||||
private static ValueTask InvokeHandlersSequentially(
|
||||
IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||
{
|
||||
var enumerator = new HandlerEnumerator(clientChannel, e, funcs);
|
||||
return enumerator.MoveNextAsync();
|
||||
}
|
||||
|
||||
private struct HandlerEnumerator
|
||||
{
|
||||
private readonly IClientChannel _channel;
|
||||
private readonly ReceivedDataEventArgs _e;
|
||||
private readonly ChannelReceivedEventHandler _funcs;
|
||||
private int _index;
|
||||
|
||||
public HandlerEnumerator(IClientChannel channel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||
static async PooledValueTask OnChannelReceivedEvent(IClientChannel clientChannel, ReceivedDataEventArgs e, ChannelReceivedEventHandler funcs)
|
||||
{
|
||||
_channel = channel;
|
||||
_e = e;
|
||||
_funcs = funcs;
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
public ValueTask MoveNextAsync()
|
||||
{
|
||||
_index++;
|
||||
if (_index >= _funcs.Count) return default;
|
||||
|
||||
var func = _funcs[_index];
|
||||
if (func == null) return MoveNextAsync();
|
||||
|
||||
bool isLast = _index == _funcs.Count - 1;
|
||||
var vt = func.Invoke(_channel, _e, isLast);
|
||||
if (vt.IsCompletedSuccessfully)
|
||||
if (funcs.Count > 0)
|
||||
{
|
||||
if (_e.Handled) return default;
|
||||
return MoveNextAsync();
|
||||
for (int i = 0; i < funcs.Count; i++)
|
||||
{
|
||||
var func = funcs[i];
|
||||
if (func == null) continue;
|
||||
var taskResult = func.Invoke(clientChannel, e, i == funcs.Count - 1);
|
||||
if (!taskResult.IsCompletedSuccessfully)
|
||||
{
|
||||
await taskResult.ConfigureAwait(false);
|
||||
}
|
||||
if (e.Handled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Awaited(vt);
|
||||
}
|
||||
|
||||
private async ValueTask Awaited(ValueTask vt)
|
||||
{
|
||||
await vt.ConfigureAwait(false);
|
||||
if (!_e.Handled)
|
||||
await MoveNextAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,6 @@ public class TextFileLogger : ThingsGateway.NewLife.Log.TextFileLog, TouchSocket
|
||||
// 推入队列
|
||||
Enqueue(stringBuilder.ToString());
|
||||
|
||||
WriteLog();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -43,6 +43,16 @@ public struct OperResult<T> : IOperResult<T>
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
/// <summary>
|
||||
/// 从另一个操作对象中赋值信息
|
||||
/// </summary>
|
||||
public OperResult(OperResult operResult)
|
||||
{
|
||||
OperCode = operResult.OperCode;
|
||||
ErrorMessage = operResult.ErrorMessage;
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入错误信息
|
||||
@@ -129,8 +139,15 @@ public struct OperResult<T> : IOperResult<T>
|
||||
/// <param name="operResult"></param>
|
||||
public static implicit operator OperResult(OperResult<T> operResult)
|
||||
{
|
||||
return new OperResult(operResult);
|
||||
return new OperResult
|
||||
{
|
||||
OperCode = operResult.OperCode,
|
||||
ErrorMessage = operResult.ErrorMessage,
|
||||
Exception = operResult.Exception,
|
||||
ErrorType = operResult.ErrorType
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -162,7 +179,13 @@ public struct OperResult<T, T2> : IOperResult<T, T2>
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
|
||||
public OperResult(OperResult operResult)
|
||||
{
|
||||
OperCode = operResult.OperCode;
|
||||
ErrorMessage = operResult.ErrorMessage;
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
/// <summary>
|
||||
/// 传入错误信息
|
||||
/// </summary>
|
||||
@@ -275,7 +298,13 @@ public struct OperResult<T, T2, T3> : IOperResult<T, T2, T3>
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
|
||||
public OperResult(OperResult operResult)
|
||||
{
|
||||
OperCode = operResult.OperCode;
|
||||
ErrorMessage = operResult.ErrorMessage;
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
/// <summary>
|
||||
/// 传入错误信息
|
||||
/// </summary>
|
||||
@@ -389,7 +418,13 @@ public struct OperResult : IOperResult
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
|
||||
public OperResult(OperResult operResult)
|
||||
{
|
||||
OperCode = operResult.OperCode;
|
||||
ErrorMessage = operResult.ErrorMessage;
|
||||
Exception = operResult.Exception;
|
||||
ErrorType = operResult.ErrorType;
|
||||
}
|
||||
/// <summary>
|
||||
/// 传入错误信息
|
||||
/// </summary>
|
||||
|
||||
@@ -15,7 +15,6 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using System.ComponentModel;
|
||||
using ThingsGateway.NewLife.DictionaryExtensions;
|
||||
|
||||
using ThingsGateway.FriendlyException;
|
||||
|
||||
|
||||
@@ -380,7 +380,7 @@ public abstract partial class CollectBase : DriverBase
|
||||
if (cancellationToken.IsCancellationRequested) return;
|
||||
CancellationToken readToken = default;
|
||||
var readerLockTask = @this.ReadWriteLock.ReaderLockAsync(cancellationToken);
|
||||
if (!readerLockTask.IsCompleted)
|
||||
if (!readerLockTask.IsCompletedSuccessfully)
|
||||
{
|
||||
readToken = await readerLockTask.ConfigureAwait(false);
|
||||
}
|
||||
@@ -403,7 +403,7 @@ public abstract partial class CollectBase : DriverBase
|
||||
|
||||
OperResult<ReadOnlyMemory<byte>> readResult = default;
|
||||
var readTask = @this.ReadSourceAsync(variableSourceRead, allToken);
|
||||
if (!readTask.IsCompleted)
|
||||
if (!readTask.IsCompletedSuccessfully)
|
||||
{
|
||||
readResult = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
@@ -435,7 +435,7 @@ public abstract partial class CollectBase : DriverBase
|
||||
//if (LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// LogMessage?.Trace(string.Format("{0} - Collecting [{1} - {2}]", DeviceName, variableSourceRead?.RegisterAddress, variableSourceRead?.Length));
|
||||
var readTask1 = @this.ReadSourceAsync(variableSourceRead, allToken);
|
||||
if (!readTask1.IsCompleted)
|
||||
if (!readTask1.IsCompletedSuccessfully)
|
||||
{
|
||||
readResult = await readTask1.ConfigureAwait(false);
|
||||
}
|
||||
@@ -490,195 +490,6 @@ public abstract partial class CollectBase : DriverBase
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// private ValueTask ReadVariableSource(object? state, CancellationToken cancellationToken)
|
||||
// {
|
||||
// var enumerator = new ReadVariableSourceEnumerator(this, state, cancellationToken);
|
||||
// return enumerator.MoveNextAsync();
|
||||
// }
|
||||
|
||||
// private struct ReadVariableSourceEnumerator
|
||||
// {
|
||||
// private readonly CollectBase _owner;
|
||||
// private readonly object? _state;
|
||||
// private readonly CancellationToken _cancellationToken;
|
||||
|
||||
// private VariableSourceRead _variableSourceRead;
|
||||
// private CancellationToken _readToken;
|
||||
// private CancellationToken _allToken;
|
||||
// private OperResult<ReadOnlyMemory<byte>> _readResult;
|
||||
// private int _readErrorCount;
|
||||
// private ValueTask<CancellationToken> _readerLockTask;
|
||||
// private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask;
|
||||
// private int _step;
|
||||
|
||||
// public ReadVariableSourceEnumerator(CollectBase owner, object? state, CancellationToken cancellationToken)
|
||||
// {
|
||||
// _owner = owner;
|
||||
// _state = state;
|
||||
// _cancellationToken = cancellationToken;
|
||||
|
||||
// _variableSourceRead = default!;
|
||||
// _readToken = default;
|
||||
// _allToken = default;
|
||||
// _readResult = default;
|
||||
// _readErrorCount = 0;
|
||||
// _readerLockTask = default;
|
||||
// _readTask = default;
|
||||
// _step = 0;
|
||||
// }
|
||||
|
||||
// public ValueTask MoveNextAsync()
|
||||
// {
|
||||
// switch (_step)
|
||||
// {
|
||||
// case 0:
|
||||
// if (_state is not VariableSourceRead vsr) return default;
|
||||
// _variableSourceRead = vsr;
|
||||
|
||||
// if (_owner.Pause) return default;
|
||||
// if (_cancellationToken.IsCancellationRequested) return default;
|
||||
|
||||
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
// _readerLockTask = _owner.ReadWriteLock.ReaderLockAsync(_cancellationToken);
|
||||
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
// if (!_readerLockTask.IsCompleted)
|
||||
// {
|
||||
// _step = 1;
|
||||
// return AwaitReaderLock();
|
||||
// }
|
||||
// _readToken = _readerLockTask.Result;
|
||||
// goto case 2;
|
||||
|
||||
// case 1:
|
||||
// _readToken = _readerLockTask.Result;
|
||||
// goto case 2;
|
||||
|
||||
// case 2:
|
||||
// if (_readToken.IsCancellationRequested)
|
||||
// {
|
||||
// return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||
// }
|
||||
|
||||
// var allTokenSource = _owner._linkedCtsCache.GetLinkedTokenSource(_cancellationToken, _readToken);
|
||||
// _allToken = allTokenSource.Token;
|
||||
|
||||
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
// _readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken);
|
||||
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
// if (!_readTask.IsCompleted)
|
||||
// {
|
||||
// _step = 3;
|
||||
// return AwaitRead();
|
||||
// }
|
||||
// _readResult = _readTask.Result;
|
||||
// goto case 4;
|
||||
|
||||
// case 3:
|
||||
// _readResult = _readTask.Result;
|
||||
// goto case 4;
|
||||
|
||||
// case 4:
|
||||
// while (!_readResult.IsSuccess && _readErrorCount < _owner.CollectProperties.RetryCount)
|
||||
// {
|
||||
// if (_owner.Pause) return default;
|
||||
// if (_cancellationToken.IsCancellationRequested) return default;
|
||||
|
||||
// if (_readToken.IsCancellationRequested)
|
||||
// {
|
||||
// return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||
// }
|
||||
|
||||
// _readErrorCount++;
|
||||
// if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] failed - {3}",
|
||||
// _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||
|
||||
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
// _readTask = _owner.ReadSourceAsync(_variableSourceRead, _allToken);
|
||||
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
// if (!_readTask.IsCompleted)
|
||||
// {
|
||||
// _step = 5;
|
||||
// return AwaitReadRetry();
|
||||
// }
|
||||
// _readResult = _readTask.Result;
|
||||
// }
|
||||
|
||||
// goto case 6;
|
||||
|
||||
// case 5:
|
||||
// _readResult = _readTask.Result;
|
||||
// _step = 4;
|
||||
// return MoveNextAsync();
|
||||
|
||||
// case 6:
|
||||
// if (_readResult.IsSuccess)
|
||||
// {
|
||||
// if (_owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data succeeded {3}",
|
||||
// _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.Content.Span.ToHexString(' ')));
|
||||
|
||||
// _owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (_cancellationToken.IsCancellationRequested) return default;
|
||||
// if (_readToken.IsCancellationRequested)
|
||||
// {
|
||||
// return _owner.ReadVariableSource(_state, _cancellationToken);
|
||||
// }
|
||||
|
||||
// if (_variableSourceRead.LastErrorMessage != _readResult.ErrorMessage)
|
||||
// {
|
||||
// if (!_cancellationToken.IsCancellationRequested)
|
||||
// _owner.LogMessage?.LogWarning(_readResult.Exception, string.Format(AppResource.CollectFail, _owner.DeviceName,
|
||||
// _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (!_cancellationToken.IsCancellationRequested && _owner.LogMessage?.LogLevel <= TouchSocket.Core.LogLevel.Trace)
|
||||
// _owner.LogMessage?.Trace(string.Format("{0} - Collection [{1} - {2}] data failed - {3}",
|
||||
// _owner.DeviceName, _variableSourceRead?.RegisterAddress, _variableSourceRead?.Length, _readResult.ErrorMessage));
|
||||
// }
|
||||
|
||||
// _variableSourceRead.LastErrorMessage = _readResult.ErrorMessage;
|
||||
// _owner.CurrentDevice.SetDeviceStatus(TimerX.Now, null, _readResult.ErrorMessage);
|
||||
// var time = DateTime.Now;
|
||||
// foreach (var item in _variableSourceRead.VariableRuntimes)
|
||||
// {
|
||||
// item.SetValue(null, time, isOnline: false);
|
||||
// }
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
|
||||
// return default;
|
||||
// }
|
||||
|
||||
// private async ValueTask AwaitReaderLock()
|
||||
// {
|
||||
// await _readerLockTask.ConfigureAwait(false);
|
||||
// _step = 1;
|
||||
// await MoveNextAsync().ConfigureAwait(false);
|
||||
// }
|
||||
|
||||
// private async ValueTask AwaitRead()
|
||||
// {
|
||||
// await _readTask.ConfigureAwait(false);
|
||||
// _step = 3;
|
||||
// await MoveNextAsync().ConfigureAwait(false);
|
||||
// }
|
||||
|
||||
// private async ValueTask AwaitReadRetry()
|
||||
// {
|
||||
// await _readTask.ConfigureAwait(false);
|
||||
// _step = 5;
|
||||
// await MoveNextAsync().ConfigureAwait(false);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ public abstract class CollectFoundationBase : CollectBase
|
||||
// 从协议读取数据
|
||||
OperResult<ReadOnlyMemory<byte>> read = default;
|
||||
var readTask = @this.FoundationDevice.ReadAsync(variableSourceRead.AddressObject, cancellationToken);
|
||||
if (!readTask.IsCompleted)
|
||||
if (!readTask.IsCompletedSuccessfully)
|
||||
{
|
||||
read = await readTask.ConfigureAwait(false);
|
||||
}
|
||||
@@ -200,116 +200,6 @@ public abstract class CollectFoundationBase : CollectBase
|
||||
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// 采集驱动读取,读取成功后直接赋值变量,失败不做处理,注意非通用设备需重写
|
||||
///// </summary>
|
||||
// protected override ValueTask<OperResult<ReadOnlyMemory<byte>>> ReadSourceAsync(VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||
// {
|
||||
// if (cancellationToken.IsCancellationRequested)
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>( new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException()));
|
||||
|
||||
// // 值类型状态机
|
||||
// var stateMachine = new ReadSourceStateMachine(this, variableSourceRead, cancellationToken);
|
||||
// return stateMachine.MoveNextAsync();
|
||||
// }
|
||||
|
||||
// private struct ReadSourceStateMachine
|
||||
// {
|
||||
// private readonly VariableSourceRead _variableSourceRead;
|
||||
// private readonly CancellationToken _cancellationToken;
|
||||
// private readonly CollectFoundationBase _owner;
|
||||
// private OperResult<ReadOnlyMemory<byte>> _result;
|
||||
// private ValueTask<OperResult<ReadOnlyMemory<byte>>> _readTask;
|
||||
|
||||
// public ReadSourceStateMachine(CollectFoundationBase owner, VariableSourceRead variableSourceRead, CancellationToken cancellationToken)
|
||||
// {
|
||||
// _owner = owner;
|
||||
// _variableSourceRead = variableSourceRead;
|
||||
// _cancellationToken = cancellationToken;
|
||||
// _result = default;
|
||||
// State = 0;
|
||||
// }
|
||||
|
||||
// public int State { get; private set; }
|
||||
|
||||
// public ValueTask<OperResult<ReadOnlyMemory<byte>>> MoveNextAsync()
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// switch (State)
|
||||
// {
|
||||
// case 0:
|
||||
// // 异步读取
|
||||
// if (_cancellationToken.IsCancellationRequested)
|
||||
// {
|
||||
// _result = new OperResult<ReadOnlyMemory<byte>>(new OperationCanceledException());
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||
// }
|
||||
|
||||
//#pragma warning disable CA2012 // 正确使用 ValueTask
|
||||
// _readTask = _owner.FoundationDevice.ReadAsync(_variableSourceRead.AddressObject, _cancellationToken);
|
||||
//#pragma warning restore CA2012 // 正确使用 ValueTask
|
||||
|
||||
// // 检查是否任务已完成
|
||||
// if (_readTask.IsCompleted)
|
||||
// {
|
||||
// _result = _readTask.Result;
|
||||
// State = 1;
|
||||
// return MoveNextAsync();
|
||||
// }
|
||||
|
||||
// // 如果任务尚未完成,继续等待
|
||||
// State = 2;
|
||||
// return Awaited(_readTask);
|
||||
|
||||
// case 1:
|
||||
// // 解析结构化内容
|
||||
// if (_result.IsSuccess)
|
||||
// {
|
||||
// var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false);
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult));
|
||||
// }
|
||||
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||
|
||||
// case 2:
|
||||
// // 完成任务后,解析内容
|
||||
// _result = _readTask.Result;
|
||||
|
||||
// if (_result.IsSuccess)
|
||||
// {
|
||||
// var parsedResult = _variableSourceRead.VariableRuntimes.PraseStructContent(_owner.FoundationDevice, _result.Content.Span, false);
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(parsedResult));
|
||||
// }
|
||||
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(_result);
|
||||
|
||||
// default:
|
||||
// throw new InvalidOperationException("Unexpected state.");
|
||||
// }
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// return new ValueTask<OperResult<ReadOnlyMemory<byte>>>(new OperResult<ReadOnlyMemory<byte>>(ex));
|
||||
// }
|
||||
// }
|
||||
|
||||
// private async ValueTask<OperResult<ReadOnlyMemory<byte>>> Awaited(ValueTask<OperResult<ReadOnlyMemory<byte>>> vt)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
|
||||
|
||||
// await vt.ConfigureAwait(false);
|
||||
// return await MoveNextAsync().ConfigureAwait(false);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// return new OperResult<ReadOnlyMemory<byte>>(ex);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// 批量写入变量值,需返回变量名称/结果,注意非通用设备需重写
|
||||
/// </summary>
|
||||
|
||||
@@ -327,9 +327,12 @@ public static class GlobalData
|
||||
{
|
||||
if (deviceRuntime.RedundantEnable && deviceRuntime.RedundantDeviceId != null)
|
||||
return true;
|
||||
else if (GlobalData.IdDevices.Any(a => a.Value.RedundantDeviceId == deviceRuntime.Id))
|
||||
|
||||
var id = deviceRuntime.Id;
|
||||
foreach (var kv in GlobalData.IdDevices)
|
||||
{
|
||||
return true;
|
||||
if (kv.Value.RedundantDeviceId == id)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -424,7 +424,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
if (differences?.Count > 0)
|
||||
{
|
||||
var data = models.ToList();
|
||||
await GlobalData.CheckByDeviceIds(data.Select(a => a.DeviceId)).ConfigureAwait(false);
|
||||
await GlobalData.CheckByDeviceIds(data.Select(a => a.DeviceId)).ConfigureAwait(false);
|
||||
using var db = GetDB();
|
||||
|
||||
var result = (await db.Updateable(data).UpdateColumns(differences.Select(a => a.Key).ToArray()).ExecuteCommandAsync().ConfigureAwait(false)) > 0;
|
||||
@@ -493,10 +493,10 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
private async Task<Func<ISugarQueryable<Variable>, ISugarQueryable<Variable>>> GetWhereQueryFunc(GatewayExportFilter exportFilter)
|
||||
{
|
||||
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
|
||||
List<long>? filterDeviceIds= null;
|
||||
if(dataScope!=null)
|
||||
List<long>? filterDeviceIds = null;
|
||||
if (dataScope != null)
|
||||
{
|
||||
filterDeviceIds= GlobalData.GetCurrentUserDeviceIds(dataScope).ToList();
|
||||
filterDeviceIds = GlobalData.GetCurrentUserDeviceIds(dataScope).ToList();
|
||||
}
|
||||
HashSet<long>? deviceId = null;
|
||||
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
|
||||
@@ -513,7 +513,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Collect, a => a.DeviceId == exportFilter.DeviceId)
|
||||
.WhereIF(deviceId != null, a => deviceId.Contains(a.DeviceId))
|
||||
|
||||
.WhereIF(filterDeviceIds != null , u => filterDeviceIds.Contains(u.DeviceId))//在指定机构列表查询
|
||||
.WhereIF(filterDeviceIds != null, u => filterDeviceIds.Contains(u.DeviceId))//在指定机构列表查询
|
||||
|
||||
.WhereIF(exportFilter.PluginType == PluginTypeEnum.Business, u => SqlFunc.JsonLike(u.VariablePropertys, exportFilter.DeviceId.ToString()));
|
||||
return whereQuery;
|
||||
@@ -527,8 +527,8 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
{
|
||||
filterDeviceIds = GlobalData.GetCurrentUserDeviceIds(dataScope).ToList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
HashSet<long>? deviceId = null;
|
||||
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
|
||||
{
|
||||
@@ -850,7 +850,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
|
||||
{
|
||||
variable.IsUp = false;
|
||||
}
|
||||
|
||||
|
||||
if (device.IsUp && (filterDeviceIds?.Contains(variable.DeviceId) != false))
|
||||
{
|
||||
importPreviewOutput.Results.Add(new(Interlocked.Increment(ref row), false, "Operation not permitted"));
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Rougamo.Fody" Version="5.0.2" />
|
||||
<PackageReference Include="TouchSocket.Dmtp" Version="$(TSVersion)" />
|
||||
<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="$(TSVersion)" />-->
|
||||
|
||||
@@ -1347,27 +1347,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
|
||||
private TreeViewItem<ChannelDeviceTreeItem> UnknownTreeViewItem;
|
||||
|
||||
SmartTriggerScheduler? scheduler;
|
||||
private bool _initialized;
|
||||
public override async Task SetParametersAsync(ParameterView parameters)
|
||||
{
|
||||
parameters.SetParameterProperties(this);
|
||||
if (!_initialized)
|
||||
{
|
||||
_initialized = true;
|
||||
|
||||
OnInitialized();
|
||||
await OnInitializedAsync();
|
||||
OnParametersSet();
|
||||
StateHasChanged();
|
||||
await OnParametersSetAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnParametersSet();
|
||||
StateHasChanged();
|
||||
await OnParametersSetAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ThingsGateway.Blazor.Diagrams\ThingsGateway.Blazor.Diagrams.csproj" />
|
||||
<ProjectReference Include="..\ThingsGateway.Gateway.Application\ThingsGateway.Gateway.Application.csproj" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<ProjectReference Include="..\..\Admin\ThingsGateway.Admin.Razor\ThingsGateway.Admin.Razor.csproj" />
|
||||
<ProjectReference Include="..\..\Foundation\ThingsGateway.Foundation.Razor\ThingsGateway.Foundation.Razor.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -7,15 +7,13 @@
|
||||
<OutputType>WinExe</OutputType>
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
<TargetFrameworks>net8.0;$(OtherTargetFrameworks);</TargetFrameworks>
|
||||
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>true</GarbageCollectionAdaptationMode>
|
||||
|
||||
<!--使用自托管线程池-->
|
||||
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
|
||||
// 此代码版权(除特别声明外的代码)归作者本人Diego所有
|
||||
// 源代码使用协议遵循本仓库的开源协议及附加协议
|
||||
// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
|
||||
// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
|
||||
// 使用文档:https://thingsgateway.cn/
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
namespace ThingsGateway.Foundation.Modbus;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -558,7 +558,7 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryParseRequest(object requestInfo, out ModbusRequest modbusRequest, out ReadOnlySequence<byte> sequences, out bool modbusRtu)
|
||||
private static bool TryParseRequest(IRequestInfo requestInfo, out ModbusRequest modbusRequest, out ReadOnlySequence<byte> sequences, out bool modbusRtu)
|
||||
{
|
||||
modbusRequest = default;
|
||||
sequences = default;
|
||||
@@ -599,25 +599,29 @@ public class ModbusSlave : DeviceBase, IModbusAddress
|
||||
return WriteError(modbusRtu, client, sequences, e);
|
||||
}
|
||||
|
||||
return Write(this, client, e, modbusRequest, sequences, modbusRtu, data);
|
||||
|
||||
static async PooledTask Write(ModbusSlave @this, IClientChannel client, ReceivedDataEventArgs e, ModbusRequest modbusRequest, ReadOnlySequence<byte> sequences, bool modbusRtu, OperResult<ReadOnlyMemory<byte>> data)
|
||||
ValueByteBlock byteBlock = new(1024);
|
||||
try
|
||||
{
|
||||
WriteReadResponse(modbusRequest, sequences, data.Content, ref byteBlock, modbusRtu);
|
||||
return SendAndDisposeAsync(this, client, e, modbusRtu, byteBlock);
|
||||
}
|
||||
catch
|
||||
{
|
||||
byteBlock.SafeDispose();
|
||||
return WriteError(modbusRtu, client, sequences, e);
|
||||
}
|
||||
static async PooledTask SendAndDisposeAsync(ModbusSlave @this, IClientChannel client, ReceivedDataEventArgs e, bool modbusRtu, ValueByteBlock byteBlock)
|
||||
{
|
||||
ValueByteBlock byteBlock = new(1024);
|
||||
try
|
||||
{
|
||||
WriteReadResponse(modbusRequest, sequences, data.Content, ref byteBlock, modbusRtu);
|
||||
await @this.ReturnData(client, byteBlock.Memory, e).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await @this.WriteError(modbusRtu, client, sequences, e).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
byteBlock.SafeDispose();
|
||||
byteBlock.SafeDispose(); //即使 ValueByteBlock 是 struct,内部仍持有同一块 Memory<byte> 引用,可以安全释放
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Task HandleWriteRequestAsync(
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
<ProjectReference Include="..\..\Gateway\ThingsGateway.Gateway.Razor\ThingsGateway.Gateway.Razor.csproj">
|
||||
</ProjectReference>
|
||||
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.3.0" ExcludeAssets="runtime" PrivateAssets="all">
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -64,12 +64,10 @@
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
<!--<PublishAot>true</PublishAot>-->
|
||||
<CETCompat>false</CETCompat>
|
||||
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
<!--<PlatformTarget>x86</PlatformTarget>-->
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<Import Project="..\Version.props" />
|
||||
|
||||
@@ -37,17 +37,15 @@
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
<!--<PublishAot>true</PublishAot>-->
|
||||
<CETCompat>false</CETCompat>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
|
||||
<PublishAot>true</PublishAot>
|
||||
<DebugType>none</DebugType>
|
||||
<EmbedAllSources>false</EmbedAllSources>
|
||||
<EmitDebugInformation>false</EmitDebugInformation>
|
||||
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
<!--<PlatformTarget>x86</PlatformTarget>-->
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -32,26 +32,14 @@
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
|
||||
|
||||
|
||||
|
||||
<!--<TieredCompilation>false</TieredCompilation>-->
|
||||
|
||||
<!--使用自托管线程池-->
|
||||
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
|
||||
<!--<PlatformTarget>x86</PlatformTarget>-->
|
||||
<!--editbin /LARGEADDRESSAWARE:NO ThingsGateway.Server.exe-->
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
|
||||
"RemoteServerManagement": {
|
||||
"Enable": true,
|
||||
"Enable": false,
|
||||
"Name": "ThingsGateway",
|
||||
"ServerUri": "0.0.0.0:8399",
|
||||
"VerifyToken": "ThingsGateway",
|
||||
|
||||
@@ -111,7 +111,8 @@ public class Startup : AppStartup
|
||||
options.RootComponents.MaxJSRootComponents = 500;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 20;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
|
||||
options.DisconnectedCircuitMaxRetained = 1;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
|
||||
})
|
||||
.AddHubOptions(options =>
|
||||
{
|
||||
@@ -131,7 +132,8 @@ public class Startup : AppStartup
|
||||
options.RootComponents.MaxJSRootComponents = 500;
|
||||
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
|
||||
options.MaxBufferedUnacknowledgedRenderBatches = 20;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(10);
|
||||
options.DisconnectedCircuitMaxRetained = 1;
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(10);
|
||||
}).AddHubOptions(options =>
|
||||
{
|
||||
//单个传入集线器消息的最大大小。默认 32 KB
|
||||
|
||||
@@ -51,26 +51,11 @@
|
||||
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
|
||||
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<!--动态适用GC-->
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
|
||||
|
||||
<CETCompat>false</CETCompat>
|
||||
|
||||
|
||||
|
||||
|
||||
<!--<TieredCompilation>false</TieredCompilation>-->
|
||||
|
||||
<!--使用自托管线程池-->
|
||||
<!--<UseWindowsThreadPool>false</UseWindowsThreadPool> -->
|
||||
|
||||
<!--使用工作站GC-->
|
||||
<!--<ServerGarbageCollection>true</ServerGarbageCollection>-->
|
||||
|
||||
<!--<PlatformTarget>x86</PlatformTarget>-->
|
||||
<!--editbin /LARGEADDRESSAWARE:NO ThingsGateway.Server.exe-->
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"configProperties": {
|
||||
"System.Runtime.EnableWriteXorExecute": false
|
||||
"System.Runtime.EnableWriteXorExecute": false,
|
||||
"System.GC.HeapHardLimitPercent": 95, //堆限制百分比
|
||||
"System.GC.HighMemoryPercent": 90, //高内存百分比
|
||||
"System.GC.DynamicAdaptationMode": 1 //动态适应模式
|
||||
//"System.GC.RegionRange": 549755813888 //8GB, 区域范围,保留的虚拟内存,如DOCKER内出现OOM,可以调大,一般是进程内存限制的2倍
|
||||
}
|
||||
}
|
||||
|
||||
302
wiki.json
Normal file
302
wiki.json
Normal file
@@ -0,0 +1,302 @@
|
||||
{
|
||||
"repo_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
],
|
||||
"pages": [
|
||||
{
|
||||
"title": "Overview",
|
||||
"purpose": "Introduce ThingsGateway, explaining what it is, its core purpose as an industrial IoT gateway, and the high-level architecture",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Key Features",
|
||||
"purpose": "List and describe the main features including multi-protocol support, plugin architecture, data persistence options, and deployment models",
|
||||
"parent": "Overview",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "System Requirements and Dependencies",
|
||||
"purpose": "Document the .NET versions, target frameworks, required NuGet packages, and system requirements",
|
||||
"parent": "Overview",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Getting Started",
|
||||
"purpose": "Provide quick-start guide for installing and running ThingsGateway",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Installation and Deployment",
|
||||
"purpose": "Explain deployment options including Docker (x64/ARM64), web server, desktop application, and configuration basics",
|
||||
"parent": "Getting Started",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Initial Configuration",
|
||||
"purpose": "Guide users through initial setup including appsettings.json, environment variables, logging configuration, and first channel/device setup",
|
||||
"parent": "Getting Started",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Architecture Overview",
|
||||
"purpose": "Explain the overall system architecture including layered design, separation of concerns, and component relationships",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Layered Architecture",
|
||||
"purpose": "Describe the Foundation, Gateway Application, and UI/Admin layers and their responsibilities",
|
||||
"parent": "Architecture Overview",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Plugin System Architecture",
|
||||
"purpose": "Explain the plugin architecture including Foundation vs Plugin layer separation, dynamic loading with AssemblyLoadContext, and plugin types",
|
||||
"parent": "Architecture Overview",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Build System and Conditional Compilation",
|
||||
"purpose": "Document the MSBuild-based build system including Directory.Build.props, conditional plugin loading, multi-targeting, and NuGet package generation",
|
||||
"parent": "Architecture Overview",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Foundation Layer",
|
||||
"purpose": "Document the core foundation libraries that provide protocol implementations and communication primitives",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Device Communication",
|
||||
"purpose": "Explain DeviceBase abstraction, IDevice interface, protocol implementations, read/write operations, and data parsing",
|
||||
"parent": "Foundation Layer",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Channel Architecture",
|
||||
"purpose": "Document IChannel interface, channel implementations (TCP/UDP/Serial), channel options, and connection lifecycle",
|
||||
"parent": "Foundation Layer",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Data Handling and Transformation",
|
||||
"purpose": "Describe DataHandlingAdapter, message parsing, ThingsGatewayBitConverter for endianness/format conversion, and WaitHandlePool for request-response correlation",
|
||||
"parent": "Foundation Layer",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Core Utilities",
|
||||
"purpose": "Document TimerX scheduling, ExpiringDictionary caching, WaitLock concurrency primitives, and Reflect utilities",
|
||||
"parent": "Foundation Layer",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Gateway Application",
|
||||
"purpose": "Explain the main gateway application layer that orchestrates device communication and data flow",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Runtime Management System",
|
||||
"purpose": "Document the entity-to-runtime conversion, GlobalData static registry, RuntimeServiceHelper, and runtime synchronization",
|
||||
"parent": "Gateway Application",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Device Lifecycle Management",
|
||||
"purpose": "Explain DeviceThreadManage, driver initialization, StartAsync/StopAsync lifecycle, TaskSchedulerLoop, and thread management",
|
||||
"parent": "Gateway Application",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Variable Management",
|
||||
"purpose": "Document VariableRuntime, VariableSourceRead, variable packing optimization, and data collection workflow",
|
||||
"parent": "Gateway Application",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Concurrency and Read/Write Coordination",
|
||||
"purpose": "Explain AsyncReadWriteLock, reader/writer priority, duty cycle control, and concurrent operation management",
|
||||
"parent": "Gateway Application",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Event System",
|
||||
"purpose": "Document GlobalData event dispatchers, VariableValueChangeEvent, DeviceStatusChangeEvent, AlarmChangedEvent, and event flow",
|
||||
"parent": "Gateway Application",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Redundancy and Failover",
|
||||
"purpose": "Explain device redundancy system, master/slave configuration, failover triggers, and variable state transfer",
|
||||
"parent": "Gateway Application",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Plugin Development",
|
||||
"purpose": "Guide for developing custom plugins for data collection and business logic integration",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Collection Plugins (CollectBase)",
|
||||
"purpose": "Document how to create data collection plugins including protocol implementation, variable loading, and scheduled tasks",
|
||||
"parent": "Plugin Development",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Business Plugins (BusinessBase)",
|
||||
"purpose": "Explain business plugin development for data persistence, distribution, and serving protocols",
|
||||
"parent": "Plugin Development",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Plugin Service and Dynamic Loading",
|
||||
"purpose": "Document PluginService, AssemblyLoadContext usage, plugin discovery, driver instantiation, and property configuration",
|
||||
"parent": "Plugin Development",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Plugin Packaging and Distribution",
|
||||
"purpose": "Explain how to package plugins as NuGet packages, versioning, and deployment via Directory.build.targets",
|
||||
"parent": "Plugin Development",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Built-in Protocols",
|
||||
"purpose": "Document the built-in protocol implementations including Modbus, Siemens S7, OPC UA, OPC DA, and Dlt645",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Modbus Protocol",
|
||||
"purpose": "Document Modbus Master/Slave implementations, RTU/TCP/UDP support, function codes, and address parsing",
|
||||
"parent": "Built-in Protocols",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Siemens S7 Protocol",
|
||||
"purpose": "Explain S7 Master implementation, PLC types, area addressing, and data block access",
|
||||
"parent": "Built-in Protocols",
|
||||
"page_notes": [
|
||||
{
|
||||
"content": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user