Explorar el Código

update qdr.qdr.fnd.core LoopWorker,ContiguousWorker to use async methods, rewrite QMonHostBase to use LoopWorker (not complete yet)

Dalibor Votruba hace 2 años
padre
commit
b8bec03d26

+ 32 - 32
Common/qdr.fnd.core.web/qdr.fnd.core.web.csproj

@@ -1,37 +1,37 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
-    <SignAssembly>true</SignAssembly>
-    <AssemblyOriginatorKeyFile>..\bo_strong_key_pair.snk</AssemblyOriginatorKeyFile>
-    <DelaySign>false</DelaySign>
-    <RootNamespace>Quadarax.Foundation.Core.Web</RootNamespace>
-    <ApplicationIcon>..\quadarax_dll.ico</ApplicationIcon>
-    <Title>Quadarax.Foundation.Core.Web</Title>
-    <Authors>Dalibor Votruba</Authors>
-    <Company>Quadarax</Company>
-    <Product>Quadarax.Foundation</Product>
-    <Description>Quadarax.Foundation.Core.Web - UI ASP.NET layer under foundation library</Description>
-    <Copyright>Copyright © 2023 Quadarax</Copyright>
-    <PackageIcon>quadarax_dll.png</PackageIcon>
-    <PackageTags>QDR;FND;CORE;CONSOLE</PackageTags>
-    <AssemblyVersion>0.0.1.0</AssemblyVersion>
-    <FileVersion>0.0.1.0</FileVersion>
-    <Version>0.0.1.0-alpha</Version>
-  </PropertyGroup>
-
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <SignAssembly>true</SignAssembly>
+    <AssemblyOriginatorKeyFile>..\bo_strong_key_pair.snk</AssemblyOriginatorKeyFile>
+    <DelaySign>false</DelaySign>
+    <RootNamespace>Quadarax.Foundation.Core.Web</RootNamespace>
+    <ApplicationIcon>..\quadarax_dll.ico</ApplicationIcon>
+    <Title>Quadarax.Foundation.Core.Web</Title>
+    <Authors>Dalibor Votruba</Authors>
+    <Company>Quadarax</Company>
+    <Product>Quadarax.Foundation</Product>
+    <Description>Quadarax.Foundation.Core.Web - UI ASP.NET layer under foundation library</Description>
+    <Copyright>Copyright © 2023 Quadarax</Copyright>
+    <PackageIcon>quadarax_dll.png</PackageIcon>
+    <PackageTags>QDR;FND;CORE;CONSOLE</PackageTags>
+    <AssemblyVersion>0.0.1.0</AssemblyVersion>
+    <FileVersion>0.0.1.0</FileVersion>
+    <Version>0.0.1.0-alpha</Version>
+  </PropertyGroup>
+
   <ItemGroup>
     <None Include="..\quadarax_dll.png">
       <Pack>True</Pack>
       <PackagePath>\</PackagePath>
     </None>
-  </ItemGroup>
-
-  <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
-    <PackageReference Include="NLog" Version="5.2.4" />
-    <PackageReference Include="qdr.fnd.core" Version="0.0.1-alpha" />
-    <PackageReference Include="qdr.fnd.core.business" Version="0.0.1-alpha" />
-  </ItemGroup>
-
-</Project>
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
+    <PackageReference Include="NLog" Version="5.2.4" />
+    <PackageReference Include="qdr.fnd.core" Version="0.0.1-alpha" />
+    <PackageReference Include="qdr.fnd.core.business" Version="0.0.1-alpha" />
+  </ItemGroup>
+
+</Project>

+ 3 - 1
Common/qdr.fnd.core/Thread/AbstractWorker.cs

@@ -96,7 +96,9 @@ namespace Quadarax.Foundation.Core.Thread
         protected virtual void OnAfterStop()
         {
         }
+#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
         protected virtual async Task Do(IWorkerContext context)
+#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
         {
             throw new NotImplementedException("AbstractWorker.Do() must be overriden.");
         }
@@ -121,7 +123,7 @@ namespace Quadarax.Foundation.Core.Thread
         #endregion
 
         #region *** Private Operations ***
-        private async void DoInternal(object? context)
+        private async void DoInternal(object context)
         {
             var wk = (AbstractWorker)context;
             if (wk._delay != TimeSpan.Zero)

+ 5 - 5
Common/qdr.fnd.core/Thread/ContinousWorker.cs → Common/qdr.fnd.core/Thread/ContiguousWorker.cs

@@ -4,21 +4,21 @@ using Quadarax.Foundation.Core.Logging;
 
 namespace Quadarax.Foundation.Core.Thread
 {
-    public class ContinousWorker : AbstractWorker
+    public class ContiguousWorker : AbstractWorker
     {
         #region *** Private fields ***
-        private Action _body;
+        private readonly Func<Task> _body;
         #endregion
 
         #region *** Properties ***
         #endregion
 
         #region *** Constructors ***
-        public ContinousWorker(Action body = null) : base()
+        public ContiguousWorker(Func<Task> body = null) : base()
         {
             _body = body;
         }
-        public ContinousWorker(string workerName, ILogger logger = null) : base(workerName,null, logger)
+        public ContiguousWorker(string workerName, ILogger logger = null) : base(workerName,null, logger)
         {
         }
         #endregion
@@ -29,7 +29,7 @@ namespace Quadarax.Foundation.Core.Thread
         #region *** Virtuals and protected ***
         protected override async Task Do(IWorkerContext context)
         {
-            _body?.Invoke();
+            await _body.Invoke();
         }
         #endregion
 

+ 15 - 2
Common/qdr.fnd.core/Thread/LoopWorker.cs

@@ -9,6 +9,7 @@ namespace Quadarax.Foundation.Core.Thread
         #region *** Private fields ***
         private TimeSpan _defaultTimeout;
         private long _cntLimit;
+        private Func<IWorkerContext, Task> _customIteration;
         #endregion
 
         #region *** Properties ***
@@ -31,6 +32,10 @@ namespace Quadarax.Foundation.Core.Thread
         public LoopWorker(string workerName, IWorkerContext context, ILogger logger = null) : base(workerName, context, logger)
         {
         }
+        public LoopWorker(string workerName, IWorkerContext context,Func<IWorkerContext, Task> customIteration = null, ILogger logger = null) : base(workerName, context, logger)
+        {
+            _customIteration = customIteration;
+        }
         #endregion
 
         #region *** Public Operations ***
@@ -51,18 +56,26 @@ namespace Quadarax.Foundation.Core.Thread
 
                 if (IterationsLimit == 0)
                 {
-                    await DoIteration(context);
+                    if (_customIteration != null)
+                        await _customIteration(context);
+                    else
+                        await DoIteration(context);
                 }
                 else
                 {
-                    await DoIteration(context);
+                    if (_customIteration != null)
+                        await _customIteration(context);
+                    else
+                        await DoIteration(context);
                     _cntLimit++;
                     if (_cntLimit > IterationsLimit)
                         exit = true;
                 }
             }
         }
+#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
         protected virtual async Task DoIteration(IWorkerContext context)
+#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
         {
         }
 

+ 13 - 3
Common/qdr.fnd.core/qdr.fnd.core.csproj

@@ -15,12 +15,22 @@
     <Copyright>Copyright © 2023 Quadarax</Copyright>
     <PackageIcon>quadarax_dll.png</PackageIcon>
     <PackageTags>QDR;FND;CORE</PackageTags>
-    <AssemblyVersion>0.0.1.0</AssemblyVersion>
-    <FileVersion>0.0.1.0</FileVersion>
-    <Version>0.0.1.0-alpha</Version>
+    <AssemblyVersion>0.0.2.0</AssemblyVersion>
+    <FileVersion>0.0.2.0</FileVersion>
+    <Version>0.0.2.0-alpha</Version>
 
   </PropertyGroup>
 
+  <ItemGroup>
+    <None Remove="releasenotes.md" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="releasenotes.md">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
   <ItemGroup>
     <Folder Include="Data\Extensions\" />
     <Folder Include="Mapper\" />

+ 14 - 0
Common/qdr.fnd.core/releasenotes.md

@@ -0,0 +1,14 @@
+# Quadarax.Foundation.Core * Release notes
+Ordered by version descending
+
+## 0.0.2.0
+Date:__25.9.2023__
+- extends Thread/LoopWorker to support external ansync iteration body expression
+- fix Thread/ContiguousWorker to support external ansync iteration body expression
+- fix quality CA
+---
+## 0.0.1.0
+Date:__1.9.2023__
+- initial version
+---
+---

+ 1 - 1
Workbench/QMonitor/qmonlib.console/Commands/BaseCommand.cs

@@ -135,7 +135,7 @@ namespace Quadarax.Foundation.Core.QMonitor.Console.Commands
             rcvCfg.SourceUriGeneral = _uriGeneral ?? Configuration.GeneralUri;
             rcvCfg.SourceUriData = _uriData ?? Configuration.DataUri;
             WriteInfo($"Starting listener for general ({rcvCfg.SourceUriGeneral}) and data ({rcvCfg.SourceUriData}) ...");
-            Receiver = new QMonReceiver(this.FileSystem, rcvCfg,new ConsoleLogger(), OnGeneralReceived, OnDataReceived);
+            Receiver = new QMonReceiver(this.FileSystem, rcvCfg,null, OnGeneralReceived, OnDataReceived);
         }
 
         protected override Result EndExecute(Result incommingResult)

+ 28 - 8
Workbench/QMonitor/qmonlib.console/Commands/MonitorCommand.cs

@@ -1,5 +1,7 @@
 using Quadarax.Foundation.Core.QConsole;
+using Quadarax.Foundation.Core.QConsole.Argument;
 using Quadarax.Foundation.Core.QConsole.Attributes;
+using Quadarax.Foundation.Core.QConsole.Value;
 using Quadarax.Foundation.Core.Value;
 
 namespace Quadarax.Foundation.Core.QMonitor.Console.Commands
@@ -12,27 +14,45 @@ namespace Quadarax.Foundation.Core.QMonitor.Console.Commands
         private const string CS_CMD_MONITOR_NAME = "monitor";
         private const string CS_CMD_MONITOR_DESC = "Monitor specific single channel";
 
+        protected const string CS_ARG_NAME_IID = "iid";
+        private const string CS_ARG_HINT_IID = "<instance_identifier>";
+        private const string CS_ARG_DESC_IID = "Specify instance identifier to listen from.";
+
         protected const string CS_ARG_NAME_CHANNEL = "channel";
         private const string CS_ARG_HINT_CHANNEL = "<chennel>";
-        private const string CS_ARG_DESC_CHANNEL = "Specify full channel name to monitor. Channel is complex key instanceIdentifier:channelName";
-
-        protected const string CS_ARG_NAME_DEBUG = "dbg";
-        private const string CS_ARG_HINT_DEBUG = "<is_debug_mode>";
-        private const string CS_ARG_DESC_DEBUG = "Flag if set writes debug messages.";
+        private const string CS_ARG_DESC_CHANNEL = "Specify full channel name to monitor.";
 
+        
         #endregion
 
+        #region *** Properties ***
+        public override string Name => CS_CMD_MONITOR_NAME;
+        public override string Description => CS_CMD_MONITOR_DESC;
+        #endregion
 
+        #region *** Constructor ***
         public MonitorCommand(Engine engine) : base(engine)
         {
         }
+        #endregion
 
+        #region *** Execute ***
         protected override Result OnExecute()
         {
-            throw new NotImplementedException();
+            WriteInfo($"Waiting for channel definition ''");
+        }
+        #endregion
+        #region *** Private Operations ***
+        
+        protected override IEnumerable<AbstractArgument> OnSetupArguments()
+        {
+            var list = base.OnSetupArguments().ToList();
+            list.Add(new NamedArgument(CS_ARG_NAME_IID, CS_ARG_HINT_IID, CS_ARG_DESC_IID, TypeValuesEnum.String,string.Empty,true));
+            list.Add(new NamedArgument(CS_ARG_NAME_CHANNEL, CS_ARG_HINT_CHANNEL, CS_ARG_DESC_CHANNEL, TypeValuesEnum.String,string.Empty,true));
+            return list;
         }
 
-        public override string Name => CS_CMD_MONITOR_NAME;
-        public override string Description => CS_CMD_MONITOR_DESC;
+        #endregion
+        
     }
 }

+ 8 - 8
Workbench/QMonitor/qmonlib.emit/Program.cs

@@ -11,8 +11,8 @@ namespace Quadarax.Foundation.Core.QMonitor.Emit // Note: actual namespace depen
 
         static void Main(string[] args)
         {
-            Console.WriteLine("qmonlib.emit * qmon random activity generator");
-            Console.WriteLine("usage: qmonlib.emit <instance_name>");
+            System.Console.WriteLine("qmonlib.emit * qmon random activity generator");
+            System.Console.WriteLine("usage: qmonlib.emit <instance_name>");
             var instanceIdentifier = args.Length > 0 ? args[0] : "test";
 
             using (var qmonClient = new QMonClient(instanceIdentifier, QMonClientConfiuration.CreateDefault(), new ConsoleLogger()))
@@ -28,18 +28,18 @@ namespace Quadarax.Foundation.Core.QMonitor.Emit // Note: actual namespace depen
                 var tmUsers = new Timer(OnUsersTimer, ctx, TimeSpan.Zero, TimeSpan.FromSeconds(2));
 
 
-                Console.WriteLine("Press any key to stop...");
-                Console.ReadKey();
+                System.Console.WriteLine("Press any key to stop...");
+                System.Console.ReadKey();
                 tmStatus.Dispose();
                 tmProcesses.Dispose();
                 tmUsers.Dispose();
-                Console.WriteLine("Stopped.");
+                System.Console.WriteLine("Stopped.");
             }
         }
 
         private static void OnUsersTimer(object? state)
         {
-            Console.WriteLine("* OnUsersTimer");
+            System.Console.WriteLine("* OnUsersTimer");
             if (state == null) return;
             var ctx = (Context)state;
 
@@ -53,7 +53,7 @@ namespace Quadarax.Foundation.Core.QMonitor.Emit // Note: actual namespace depen
 
         private static void OnProcessesTimer(object? state)
         {
-            Console.WriteLine("* OnProcessesTimer");
+            System.Console.WriteLine("* OnProcessesTimer");
             if (state == null) return;
             var ctx = (Context)state;
 
@@ -75,7 +75,7 @@ namespace Quadarax.Foundation.Core.QMonitor.Emit // Note: actual namespace depen
 
         private static void OnServiceStatusTimer(object? state)
         {
-            Console.WriteLine("* OnServiceStatusTimer");
+            System.Console.WriteLine("* OnServiceStatusTimer");
             if (state == null) return;
             var ctx = (Context)state;
             var randomBool = _rnd.Next(2) == 1;

+ 2 - 0
Workbench/QMonitor/qmonlib.sln.DotSettings

@@ -0,0 +1,2 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=Quadarax/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

+ 1 - 1
Workbench/QMonitor/qmonlib/MonData.cs

@@ -8,7 +8,7 @@ namespace Quadarax.Foundation.Core.QMonitor
         public string InstanceIdentifier { get; set; }
 
         [JsonPropertyName("nam")]
-        public string Name { get; set; }
+        public string? Name { get; set; }
         [JsonPropertyName("tms")]
         public DateTime Timestamp { get; set; }
         [JsonPropertyName("dta")]

+ 1 - 1
Workbench/QMonitor/qmonlib/MonItemGeneral.cs

@@ -7,7 +7,7 @@ namespace Quadarax.Foundation.Core.QMonitor
     public class MonItemGeneral
     {
         [JsonPropertyName("nam")]
-        public string Name { get; set; }
+        public string? Name { get; set; }
         [JsonPropertyName("cpt")]
         public string Caption { get; set; }
         [JsonPropertyName("dsc")]

+ 17 - 19
Workbench/QMonitor/qmonlib/QMonClient.cs

@@ -1,8 +1,7 @@
-using System.ComponentModel.DataAnnotations;
-using System.IO.Abstractions;
-using System.Net;
+using System.Net;
 using System.Net.Sockets;
 using System.Reflection;
+using System.Runtime.Intrinsics.X86;
 using System.Text;
 using Quadarax.Foundation.Core.Logging;
 using Quadarax.Foundation.Core.QMonitor.Attributes;
@@ -23,8 +22,8 @@ namespace Quadarax.Foundation.Core.QMonitor
         #endregion
 
         #region *** Fields ***
-        private IDictionary<string, MonItemGeneral> _generalCache = new Dictionary<string, MonItemGeneral>();
-        private IList<MonData> _dataCache = new List<MonData>();
+        private readonly IDictionary<string, MonItemGeneral> _generalCache = new Dictionary<string, MonItemGeneral>();
+        private readonly IList<MonData> _dataCache = new List<MonData>();
         #endregion
 
         #region *** Constructors ***
@@ -46,7 +45,8 @@ namespace Quadarax.Foundation.Core.QMonitor
                     {
                         Caption = type.GetCustomAttribute<MonitoredClassAttribute>()?.Caption ?? type.Name,
                         Description = type.GetCustomAttribute<MonitoredClassAttribute>()?.Description ?? string.Empty,
-                        ViewType = MonItemGeneral.MonViewType.Item
+                        ViewType = MonItemGeneral.MonViewType.Item,
+                        Name = type.FullName,
                     };
 
                     var properties = type.GetPropertiesWithAttribute<MonitoredPropertyAttribute>(true);
@@ -84,7 +84,7 @@ namespace Quadarax.Foundation.Core.QMonitor
         #endregion
 
         #region *** Public Operations ***
-        public void Notify(object? monitoredItem)
+        public void Notify(object? monitoredItem) 
         {
             if (monitoredItem == null) return;
             var fullName = monitoredItem.GetType().FullName;
@@ -108,10 +108,8 @@ namespace Quadarax.Foundation.Core.QMonitor
             _generalCache.Clear();
         }
 
-        protected override void OnTimerData(object? state)
+        protected override async Task OnTimerData(QMonWorkerContext context)
         {
-            if (state == null) throw new ArgumentNullException(nameof(state));
-            var owner = (QMonClient)state;
             if (_dataCache.Count == 0) return;
             try
             {
@@ -119,16 +117,17 @@ namespace Quadarax.Foundation.Core.QMonitor
                 var itmCnt = 0;
                 var declaredSize = 0;
                 var sentSize = 0;
-                var dataToSent = owner._dataCache.ToArray();
+                var dataToSent = _dataCache.ToArray();
                 foreach (var data in dataToSent)
                 {
                     var dataOut = Serialize(data);
                     declaredSize += dataOut.Length;
-                    sentSize += owner.UdpClientData.Send(dataOut);
+                    ends here wants to use sendAsync
+                    sentSize += context.Client.Send(dataOut);
                     _dataCache.Remove(data);
                     itmCnt++;
                 }
-                Log(LogSeverityEnum.Trace, $"[{InstanceIdentifier}] Sent data blocks = {itmCnt} of delared {declaredSize} bytes/sent {sentSize} bytes @ {DateTime.Now.Subtract(start).TotalMilliseconds} ms.");
+                Log(LogSeverityEnum.Trace, $"[{InstanceIdentifier}] Sent data blocks = {itmCnt} of declared {declaredSize} bytes/sent {sentSize} bytes @ {DateTime.Now.Subtract(start).TotalMilliseconds} ms.");
             }
             catch (Exception ex)
             {
@@ -136,21 +135,20 @@ namespace Quadarax.Foundation.Core.QMonitor
             }
         }
 
-        protected override void OnTimerGeneral(object? state)
+        protected override async Task OnTimerGeneral(QMonWorkerContext context)
         {
-            if (state == null) throw new ArgumentNullException(nameof(state));
-            var owner = (QMonClient)state;
             try
             {
 
                 var data = new MonGeneral()
                 {
-                    InstanceIdentifier = owner.InstanceIdentifier,
+                    InstanceIdentifier = InstanceIdentifier,
                     Items = new List<MonItemGeneral>()
                 };
-                data.Items.AddRange(owner._generalCache.Values);
+                data.Items.AddRange(_generalCache.Values);
                 var dataOut = Serialize(data);
-                UdpClientGeneral.Send(dataOut);
+                ends here wants to use sendAsync
+                context.Client.Send(dataOut);
                 Log(LogSeverityEnum.Trace, $"[{InstanceIdentifier}] Sent General packet size = {dataOut.Length} bytes");
             }
             catch (Exception ex)

+ 40 - 38
Workbench/QMonitor/qmonlib/QMonHostBase.cs

@@ -1,8 +1,8 @@
 using System.IO.Abstractions;
-using System.Net;
 using System.Net.Sockets;
 using Quadarax.Foundation.Core.Logging;
 using Quadarax.Foundation.Core.Object;
+using Quadarax.Foundation.Core.Thread;
 
 namespace Quadarax.Foundation.Core.QMonitor
 {
@@ -12,14 +12,8 @@ namespace Quadarax.Foundation.Core.QMonitor
 
         #region *** Fields ***
         private bool _isEnabled;
-        private ILog? _log;
-
-        private UdpClient? _udpClientGeneral;
-        private UdpClient? _udpClientData;
-
-        private Timer? _timerGeneral;
-        private Timer? _timerData;
-
+        private readonly ILog? _log;
+        private readonly ILogger? _logger;
         #endregion
 
         #region *** Properties ***
@@ -29,8 +23,9 @@ namespace Quadarax.Foundation.Core.QMonitor
         protected abstract TimeSpan IntervalGeneral { get; }
         protected abstract TimeSpan? IntervalData { get; }
 
-        protected UdpClient UdpClientGeneral => _udpClientGeneral ?? throw new InvalidOperationException("UDP client channel General not open.");
-        protected UdpClient UdpClientData => _udpClientData ?? throw new InvalidOperationException("UDP client channel Data not open.");
+        protected LoopWorker? WorkerGeneral { get; private set; }
+        protected LoopWorker? WorkerData { get; private set; }
+
         public bool IsEnabled
         {
             get => _isEnabled;
@@ -44,6 +39,7 @@ namespace Quadarax.Foundation.Core.QMonitor
 
         protected QMonHostBase(TConfiguration configuration, ILogger? externalLogger)
         {
+            _logger = externalLogger;
             Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
             _log = externalLogger?.GetLogger(GetType());
             SetEnabled(configuration.Enabled);
@@ -55,18 +51,21 @@ namespace Quadarax.Foundation.Core.QMonitor
         {
             if (!_isEnabled) return;
 
-            if (_udpClientGeneral != null) throw new InvalidOperationException("UDP client channel General already open. Close first.");
-            if (_udpClientData != null) throw new InvalidOperationException("UDP client channel Data already open. Close first.");
+            if (WorkerGeneral != null) throw new InvalidOperationException("UDP client channel General already open. Close first.");
+            if (WorkerData != null) throw new InvalidOperationException("UDP client channel Data already open. Close first.");
 
-            _udpClientGeneral = CreateUdpClient(UriGeneral);
-            _udpClientData = CreateUdpClient(UriData);
-
-            _timerGeneral = new Timer(OnTimerGeneral, this, IntervalGeneral, IntervalGeneral);
-            Log(LogSeverityEnum.Debug, $"General timer started with interval '{IntervalGeneral}'");
+            var contextGeneral = new QMonWorkerContext(CreateUdpClient(UriGeneral));
+            WorkerGeneral = new LoopWorker("GeneralWorker", contextGeneral,async (_)=>await OnTimerGeneral(contextGeneral), _logger);
+            WorkerGeneral.IterationsDelayBetween = IntervalGeneral;
+            WorkerGeneral.Start();
+            Log(LogSeverityEnum.Debug, $"General worker started with interval '{IntervalGeneral}'");
             if (IntervalData.HasValue)
             {
-                _timerData = new Timer(OnTimerData, this, IntervalData.Value, IntervalData.Value);
-                Log(LogSeverityEnum.Debug, $"Data timer started with interval '{IntervalData}'");
+                var contextData = new QMonWorkerContext(CreateUdpClient(UriData));
+                WorkerData = new LoopWorker("GeneralWorker", contextData,async (_)=>await OnTimerData(contextData), _logger);
+                WorkerData.IterationsDelayBetween = IntervalData.Value;
+                WorkerData.Start();
+                Log(LogSeverityEnum.Debug, $"Data worker started with interval '{IntervalData}'");
             }
 
             OnOpen();
@@ -75,30 +74,33 @@ namespace Quadarax.Foundation.Core.QMonitor
         {
             if (!_isEnabled) return;
 
-            if (_udpClientGeneral != null)
-            {
-                _timerGeneral?.Dispose();
-                _udpClientGeneral.Close();
-                _udpClientGeneral.Dispose();
-                _udpClientGeneral = null;
-                _timerGeneral = null;
-            }
+            WorkerGeneral?.Stop();
+            WorkerGeneral?.Dispose();
+            WorkerGeneral = null;
+            
+            WorkerData?.Stop();
+            WorkerData?.Dispose();
+            WorkerData = null;
 
-            if (_udpClientData != null)
-            {
-                _timerData?.Dispose();
-                _udpClientData.Close();
-                _udpClientData.Dispose();
-                _udpClientData = null;
-                _timerData = null;
-            }
             OnClose();
         }
 
         protected abstract void OnOpen();
         protected abstract void OnClose();
-        protected abstract void OnTimerData(object? state);
-        protected abstract void OnTimerGeneral(object? state);
+
+#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
+        protected virtual async Task OnTimerData(QMonWorkerContext context)
+#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
+        {
+            throw new NotImplementedException();
+        }
+
+#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
+        protected virtual async Task OnTimerGeneral(QMonWorkerContext context)
+#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
+        {
+            throw new NotImplementedException();
+        }
         #endregion
 
         protected void SetEnabled(bool value)

+ 14 - 3
Workbench/QMonitor/qmonlib/QMonReceiver.cs

@@ -31,6 +31,7 @@ namespace Quadarax.Foundation.Core.QMonitor
         private JsonSerializerOptions _jsonSerializerOptions;
         private DateTime _lastSavedTimestamp;
         private IFileSystem _fileSystem;
+        private bool _isDataClosing;
         #endregion
         #region *** Constructors ***
         public QMonReceiver(IFileSystem fileSystem, QMonReceiverConfiguration configuration, ILogger logger, GeneralDataReceivedDelegate generalDataReceivedCallback = null, DataReceivedDelegate liveDataReceivedCallback = null) : base(configuration, logger)
@@ -70,6 +71,7 @@ namespace Quadarax.Foundation.Core.QMonitor
 
         protected override void OnOpen()
         {
+            _isDataClosing = false;
             var _dataReceiverCancel = new CancellationTokenSource();
             new System.Threading.Thread(() =>
             {
@@ -79,7 +81,7 @@ namespace Quadarax.Foundation.Core.QMonitor
                     while (!_dataReceiverCancel.Token.IsCancellationRequested)
                         OnThreadData(_dataReceiverCancel.Token);
                 }
-                catch (OperationCanceledException)
+                catch (OperationCanceledException) 
                 {
                     Log(LogSeverityEnum.Info, $"Data receiver thread on {UriData} unexpected stopped");
                 }
@@ -90,6 +92,7 @@ namespace Quadarax.Foundation.Core.QMonitor
 
         protected override void OnClose()
         {
+            _isDataClosing = true;
             _dataReceiverCancel?.Cancel();
             Log(LogSeverityEnum.Info, $"Data receiver thread on {UriData} stopped");
         }
@@ -137,15 +140,21 @@ namespace Quadarax.Foundation.Core.QMonitor
             owner._isReceivingGeneral = true;
             try
             {
-                var remoteEndPoint = new IPEndPoint(IPAddress.Parse(owner.UriGeneral.DnsSafeHost), owner.UriGeneral.Port);
+                var remoteEndPoint =
+                    new IPEndPoint(IPAddress.Parse(owner.UriGeneral.DnsSafeHost), owner.UriGeneral.Port);
                 var data = UdpClientGeneral.Receive(ref remoteEndPoint);
                 var general = (MonGeneral)Binder.LoadFromString(Encoding.UTF8.GetString(data), typeof(MonGeneral));
                 Log(LogSeverityEnum.Debug,
                     $"[ReceiverGeneral] receive general data from '{remoteEndPoint}' [{general.InstanceIdentifier}] size={data.Length} bytes.");
 
-                _generalCache.TryAdd(general.InstanceIdentifier, new MonReceiverGeneralCahedItem(DateTime.Now, remoteEndPoint, general));
+                _generalCache.TryAdd(general.InstanceIdentifier,
+                    new MonReceiverGeneralCahedItem(DateTime.Now, remoteEndPoint, general));
                 _generalReceivedCallback?.Invoke(general);
             }
+            catch (SocketException e)
+            {
+                if (e.ErrorCode!= 10004) Log(LogSeverityEnum.Error, $"[ReceiverGeneral] Error receiving general data", e);
+            }
             catch (Exception e)
             {
                 Log(LogSeverityEnum.Error, $"[ReceiverGeneral] Error receiving general data", e);
@@ -167,6 +176,8 @@ namespace Quadarax.Foundation.Core.QMonitor
             }
             try
             {
+                System.Threading.Thread.CurrentThread.Join(100);
+                if (_isDataClosing) return;
                 if (UdpClientData.Available == 0) return;
                 var remoteEndPoint = new IPEndPoint(IPAddress.Parse(UriData.DnsSafeHost), UriData.Port);
                 Log(LogSeverityEnum.Debug, $"[ReceiverData] Waiting for data on '{UriData}'...");

+ 39 - 0
Workbench/QMonitor/qmonlib/QMonWorkerContext.cs

@@ -0,0 +1,39 @@
+using System.Net.Sockets;
+using Quadarax.Foundation.Core.Object;
+using Quadarax.Foundation.Core.Thread;
+
+namespace Quadarax.Foundation.Core.QMonitor
+{
+    public class QMonWorkerContext : DisposableObject, IWorkerContext
+    {
+        #region *** Fields ***
+        private UdpClient? _udpClient;
+        #endregion
+
+        #region *** Properties ***
+        public UdpClient Client => _udpClient ?? throw new InvalidOperationException("UDP client channel is not open (is null).");
+        #endregion
+
+        #region *** Constructors ***
+        public QMonWorkerContext(UdpClient? udpClient)
+        {
+            SetClient(udpClient);
+        }
+        #endregion
+
+        #region *** Public Operations ***
+        public void SetClient(UdpClient? udpClient)
+        {
+            _udpClient = udpClient;
+        }
+        #endregion
+        #region *** Protected Operations ***
+        protected override void OnDisposing()
+        {
+            Client?.Close();
+            Client?.Dispose();
+            SetClient(null);
+        }
+        #endregion
+    }
+}

+ 1 - 1
Workbench/QMonitor/qmonlib/qmonlib.csproj

@@ -14,7 +14,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="qdr.fnd.core" Version="0.0.1-alpha" />
+    <PackageReference Include="qdr.fnd.core" Version="0.0.2-alpha" />
     <PackageReference Include="System.IO.Abstractions" Version="19.2.69" />
   </ItemGroup>