From 6c411d731856a0db4acc586e0efc3540365e717b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Wei=C3=9F?= <72068105+Sandoun@users.noreply.github.com> Date: Fri, 15 Jul 2022 15:53:26 +0200 Subject: [PATCH] Heavily improved the overall performance and async handling - added a queuing manager for all messages - tcp now keeps the connection after the first handshake - async tasks are now run by the time order they were added - minor bugfixes --- Examples/Program.cs | 24 +- Examples/TestRegisters.cs | 6 +- MewtocolNet/Mewtocol/DynamicInterface.cs | 190 ++++++--------- MewtocolNet/Mewtocol/MewtocolHelpers.cs | 6 +- MewtocolNet/Mewtocol/MewtocolInterface.cs | 222 +++++++++--------- .../Mewtocol/MewtocolInterfaceRequests.cs | 25 +- MewtocolNet/Mewtocol/Subregisters/Register.cs | 2 +- MewtocolNet/MewtocolNet.csproj | 2 +- MewtocolNet/Queue/SerialQueue.cs | 71 ++++++ 9 files changed, 302 insertions(+), 246 deletions(-) create mode 100644 MewtocolNet/Queue/SerialQueue.cs diff --git a/Examples/Program.cs b/Examples/Program.cs index fb02c1c..a25cbd5 100644 --- a/Examples/Program.cs +++ b/Examples/Program.cs @@ -40,18 +40,30 @@ namespace Examples { await Task.Delay(2000); - Console.WriteLine("Testregister was toggled"); + await interf.SetRegisterAsync(nameof(registers.TestInt32), 100); + + _ = Task.Factory.StartNew(async () => { + while(true) { + for (int i = 0; i < 5; i++) { + var bytes = await interf.ReadByteRange(1020, 20); + await interf.SetRegisterAsync(nameof(registers.TestBool1), !registers.TestBool1); + await interf.SetRegisterAsync(nameof(registers.TestInt32), registers.TestInt32 + 100); + await Task.Delay(1333); + } + await Task.Delay(10000); + } + }); //adds 10 each time the plc connects to the PLCs INT regíster - interf.SetRegister(nameof(registers.TestInt16), (short)(registers.TestInt16 + 10)); + //interf.SetRegister(nameof(registers.TestInt16), (short)(registers.TestInt16 + 10)); //adds 1 each time the plc connects to the PLCs DINT regíster - interf.SetRegister(nameof(registers.TestInt32), (registers.TestInt32 + 1)); + //interf.SetRegister(nameof(registers.TestInt32), (registers.TestInt32 + 1)); //adds 11.11 each time the plc connects to the PLCs REAL regíster - interf.SetRegister(nameof(registers.TestFloat32), (float)(registers.TestFloat32 + 11.11)); + //interf.SetRegister(nameof(registers.TestFloat32), (float)(registers.TestFloat32 + 11.11)); //writes 'Hello' to the PLCs string register - interf.SetRegister(nameof(registers.TestString2), "Hello"); + //interf.SetRegister(nameof(registers.TestString2), "Hello"); //set the current second to the PLCs TIME register - interf.SetRegister(nameof(registers.TestTime), TimeSpan.FromSeconds(DateTime.Now.Second)); + //interf.SetRegister(nameof(registers.TestTime), TimeSpan.FromSeconds(DateTime.Now.Second)); }); diff --git a/Examples/TestRegisters.cs b/Examples/TestRegisters.cs index 9c01b97..91db748 100644 --- a/Examples/TestRegisters.cs +++ b/Examples/TestRegisters.cs @@ -18,11 +18,11 @@ namespace Examples { public bool TestBoolInputXD { get; private set; } //corresponds to a DT1101 - DT1104 string register in the PLC with (STRING[4]) - [Register(1101, 4)] - public string TestString1 { get; private set; } + //[Register(1101, 4)] + //public string TestString1 { get; private set; } //corresponds to a DT7000 16 bit int register in the PLC - [Register(7000)] + [Register(899)] public short TestInt16 { get; private set; } //corresponds to a DTD7001 - DTD7002 32 bit int register in the PLC diff --git a/MewtocolNet/Mewtocol/DynamicInterface.cs b/MewtocolNet/Mewtocol/DynamicInterface.cs index 3d40a0a..d96cdcd 100644 --- a/MewtocolNet/Mewtocol/DynamicInterface.cs +++ b/MewtocolNet/Mewtocol/DynamicInterface.cs @@ -33,134 +33,90 @@ namespace MewtocolNet { /// internal void AttachPoller () { - if (ContinousReaderRunning) return; + if (ContinousReaderRunning) + return; - cTokenAutoUpdater = new CancellationTokenSource(); + Task.Factory.StartNew(async () => { - Logger.Log("Poller is attaching", LogLevel.Info, this); + cTokenAutoUpdater = new CancellationTokenSource(); - try { + Logger.Log("Poller is attaching", LogLevel.Info, this); - Task.Factory.StartNew(async () => { + int it = 0; - var plcinf = await GetPLCInfoAsync(); - if (plcinf == null) { - Logger.Log("PLC not reachable, stopping logger", LogLevel.Info, this); - return; - } - - PolledCycle += MewtocolInterface_PolledCycle; - void MewtocolInterface_PolledCycle () { - - StringBuilder stringBuilder = new StringBuilder(); - foreach (var reg in GetAllRegisters()) { - string address = $"{reg.GetRegisterString()}{reg.GetStartingMemoryArea()}".PadRight(8, (char)32); - stringBuilder.AppendLine($"{address}{(reg.Name != null ? $" ({reg.Name})" : "")}: {reg.GetValueString()}"); - } - - Logger.Log($"Registers loaded are: \n" + - $"--------------------\n" + - $"{stringBuilder.ToString()}" + - $"--------------------", - LogLevel.Verbose, this); - - Logger.Log("Logger did its first cycle successfully", LogLevel.Info, this); - - PolledCycle -= MewtocolInterface_PolledCycle; - } - - ContinousReaderRunning = true; - - int getPLCinfoCycleCount = 0; - - while (ContinousReaderRunning) { - - //do priority tasks first - if (PriorityTasks != null && PriorityTasks.Count > 0) { - - try { - await PriorityTasks?.FirstOrDefault(x => !x.IsCompleted); - } catch (NullReferenceException) { } - - } else if (getPLCinfoCycleCount > 25) { - - await GetPLCInfoAsync(); - getPLCinfoCycleCount = 0; - - } - - foreach (var reg in Registers) { - - if (reg is NRegister shortReg) { - var lastVal = shortReg.Value; - var readout = (await ReadNumRegister(shortReg)).Register.Value; - if (lastVal != readout) { - InvokeRegisterChanged(shortReg); - } - } - if (reg is NRegister ushortReg) { - var lastVal = ushortReg.Value; - var readout = (await ReadNumRegister(ushortReg)).Register.Value; - if (lastVal != readout) { - InvokeRegisterChanged(ushortReg); - } - } - if (reg is NRegister intReg) { - var lastVal = intReg.Value; - var readout = (await ReadNumRegister(intReg)).Register.Value; - if (lastVal != readout) { - InvokeRegisterChanged(intReg); - } - } - if (reg is NRegister uintReg) { - var lastVal = uintReg.Value; - var readout = (await ReadNumRegister(uintReg)).Register.Value; - if (lastVal != readout) { - InvokeRegisterChanged(uintReg); - } - } - if (reg is NRegister floatReg) { - var lastVal = floatReg.Value; - var readout = (await ReadNumRegister(floatReg)).Register.Value; - if (lastVal != readout) { - InvokeRegisterChanged(floatReg); - } - } - if (reg is NRegister tsReg) { - var lastVal = tsReg.Value; - var readout = (await ReadNumRegister(tsReg)).Register.Value; - if (lastVal != readout) { - InvokeRegisterChanged(tsReg); - } - } - if (reg is BRegister boolReg) { - var lastVal = boolReg.Value; - var readout = (await ReadBoolRegister(boolReg)).Register.Value; - if (lastVal != readout) { - InvokeRegisterChanged(boolReg); - } - } - if (reg is SRegister stringReg) { - var lastVal = stringReg.Value; - var readout = (await ReadStringRegister(stringReg)).Register.Value; - if (lastVal != readout) { - InvokeRegisterChanged(stringReg); - } - - } - - } - - getPLCinfoCycleCount++; + while (it < Registers.Count + 1) { + if (it >= Registers.Count) { + it = 0; //invoke cycle polled event InvokePolledCycleDone(); - + continue; } - }, cTokenAutoUpdater.Token); + var reg = Registers[it]; - } catch (TaskCanceledException) { } + if (reg is NRegister shortReg) { + var lastVal = shortReg.Value; + var readout = (await ReadNumRegister(shortReg)).Register.Value; + if (lastVal != readout) { + InvokeRegisterChanged(shortReg); + } + } + if (reg is NRegister ushortReg) { + var lastVal = ushortReg.Value; + var readout = (await ReadNumRegister(ushortReg)).Register.Value; + if (lastVal != readout) { + InvokeRegisterChanged(ushortReg); + } + } + if (reg is NRegister intReg) { + var lastVal = intReg.Value; + var readout = (await ReadNumRegister(intReg)).Register.Value; + if (lastVal != readout) { + InvokeRegisterChanged(intReg); + } + } + if (reg is NRegister uintReg) { + var lastVal = uintReg.Value; + var readout = (await ReadNumRegister(uintReg)).Register.Value; + if (lastVal != readout) { + InvokeRegisterChanged(uintReg); + } + } + if (reg is NRegister floatReg) { + var lastVal = floatReg.Value; + var readout = (await ReadNumRegister(floatReg)).Register.Value; + if (lastVal != readout) { + InvokeRegisterChanged(floatReg); + } + } + if (reg is NRegister tsReg) { + var lastVal = tsReg.Value; + var readout = (await ReadNumRegister(tsReg)).Register.Value; + if (lastVal != readout) { + InvokeRegisterChanged(tsReg); + } + } + if (reg is BRegister boolReg) { + var lastVal = boolReg.Value; + var readout = (await ReadBoolRegister(boolReg)).Register.Value; + if (lastVal != readout) { + InvokeRegisterChanged(boolReg); + } + } + if (reg is SRegister stringReg) { + var lastVal = stringReg.Value; + var readout = (await ReadStringRegister(stringReg)).Register.Value; + if (lastVal != readout) { + InvokeRegisterChanged(stringReg); + } + } + + it++; + + } + + }); } diff --git a/MewtocolNet/Mewtocol/MewtocolHelpers.cs b/MewtocolNet/Mewtocol/MewtocolHelpers.cs index 5868c63..fbcd79e 100644 --- a/MewtocolNet/Mewtocol/MewtocolHelpers.cs +++ b/MewtocolNet/Mewtocol/MewtocolHelpers.cs @@ -75,7 +75,7 @@ namespace MewtocolNet { var res = new Regex(@"\%([0-9]{2})\$RD.{8}(.*)...").Match(_onString); if(res.Success) { string val = res.Groups[2].Value; - return val.GetStringFromAsciiHex().Trim(); + return val.GetStringFromAsciiHex()?.Trim(); } return null; } @@ -143,7 +143,7 @@ namespace MewtocolNet { internal static string GetStringFromAsciiHex (this string input) { if (input.Length % 2 != 0) - throw new ArgumentException("input not a hex string"); + return null; byte[] bytes = new byte[input.Length / 2]; for (int i = 0; i < input.Length; i += 2) { String hex = input.Substring(i, 2); @@ -158,6 +158,8 @@ namespace MewtocolNet { } internal static byte[] HexStringToByteArray(this string hex) { + if (hex == null) + return null; return Enumerable.Range(0, hex.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) diff --git a/MewtocolNet/Mewtocol/MewtocolInterface.cs b/MewtocolNet/Mewtocol/MewtocolInterface.cs index f92fbe5..f7784b7 100644 --- a/MewtocolNet/Mewtocol/MewtocolInterface.cs +++ b/MewtocolNet/Mewtocol/MewtocolInterface.cs @@ -12,6 +12,9 @@ using MewtocolNet.Logging; using System.Collections; using System.Diagnostics; using System.ComponentModel; +using System.Net; +using System.Threading; +using MewtocolNet.Queue; namespace MewtocolNet { @@ -86,7 +89,7 @@ namespace MewtocolNet { /// public int StationNumber => stationNumber; - private int cycleTimeMs; + private int cycleTimeMs = 25; /// /// The duration of the last message cycle /// @@ -98,9 +101,10 @@ namespace MewtocolNet { } } - - internal List PriorityTasks { get; set; } = new List(); - + internal NetworkStream stream; + internal TcpClient client; + internal readonly SerialQueue queue = new SerialQueue(); + private int RecBufferSize = 64; internal int SendExceptionsInRow = 0; #region Initialization @@ -205,8 +209,6 @@ namespace MewtocolNet { OnMajorSocketException(); - PriorityTasks.Clear(); - } /// @@ -335,9 +337,12 @@ namespace MewtocolNet { var bytes = BitConverter.GetBytes(reg16.Value); BitArray bitAr = new BitArray(bytes); - prop.SetValue(collection, bitAr[bitIndex]); - collection.TriggerPropertyChanged(prop.Name); + if (bitIndex < bitAr.Length && bitIndex >= 0) { + prop.SetValue(collection, bitAr[bitIndex]); + collection.TriggerPropertyChanged(prop.Name); + } + } else if (bitWiseFound != null && reg is NRegister reg32) { var casted = (RegisterAttribute)bitWiseFound; var bitIndex = casted.AssignedBitIndex; @@ -542,6 +547,25 @@ namespace MewtocolNet { #region Low level command handling + private async Task ConnectTCP () { + + var targetIP = IPAddress.Parse(ip); + + client = new TcpClient() { + ReceiveBufferSize = RecBufferSize, + NoDelay = false, + ExclusiveAddressUse = true + }; + + await client.ConnectAsync(targetIP, port); + + stream = client.GetStream(); + stream.ReadTimeout = 1000; + + Console.WriteLine($"Connected {client.Connected}"); + + } + /// /// Calculates checksum and sends a command to the PLC then awaits results /// @@ -553,29 +577,42 @@ namespace MewtocolNet { _msg += "\r"; //send request + try { - string response = null; + var response = await queue.Enqueue(() => SendSingleBlock(_msg)); - if (ContinousReaderRunning) { + if (response == null) { + return new CommandResult { + Success = false, + Error = "0000", + ErrorDescription = "null result" + }; + } - //if the poller is active then add all messages to a qeueue + //error catching + Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase); + Match m = errorcheck.Match(response.ToString()); + if (m.Success) { + string eCode = m.Groups[1].Value; + string eDes = Links.LinkedData.ErrorCodes[Convert.ToInt32(eCode)]; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Response is: {response}"); + Logger.Log($"Error on command {_msg.Replace("\r", "")} the PLC returned error code: {eCode}, {eDes}", LogLevel.Error); + Console.ResetColor(); + return new CommandResult { + Success = false, + Error = eCode, + ErrorDescription = eDes + }; + } - var awaittask = SendSingleBlock(_msg); - PriorityTasks.Add(awaittask); - await awaittask; + return new CommandResult { + Success = true, + Error = "0000", + Response = response.ToString() + }; - PriorityTasks.Remove(awaittask); - response = awaittask.Result; - - } else { - - //poller not active let the user manage message timing - - response = await SendSingleBlock(_msg); - - } - - if (response == null) { + } catch { return new CommandResult { Success = false, Error = "0000", @@ -583,97 +620,63 @@ namespace MewtocolNet { }; } - //error catching - Regex errorcheck = new Regex(@"\%[0-9]{2}\!([0-9]{2})", RegexOptions.IgnoreCase); - Match m = errorcheck.Match(response.ToString()); - if (m.Success) { - string eCode = m.Groups[1].Value; - string eDes = Links.LinkedData.ErrorCodes[Convert.ToInt32(eCode)]; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Response is: {response}"); - Console.WriteLine($"Error on command {_msg.Replace("\r", "")} the PLC returned error code: {eCode}, {eDes}"); - Console.ResetColor(); - return new CommandResult { - Success = false, - Error = eCode, - ErrorDescription = eDes - }; - } - - return new CommandResult { - Success = true, - Error = "0000", - Response = response.ToString() - }; - } private async Task SendSingleBlock (string _blockString) { - Stopwatch sw = Stopwatch.StartNew(); - - using (TcpClient client = new TcpClient() { ReceiveBufferSize = 64, NoDelay = true, ExclusiveAddressUse = true }) { - - try { - - await client.ConnectAsync(ip, port); - - using (NetworkStream stream = client.GetStream()) { - - var message = _blockString.ToHexASCIIBytes(); - var messageAscii = BitConverter.ToString(message).Replace("-", " "); - - //send request - try { - using (var sendStream = new MemoryStream(message)) { - await sendStream.CopyToAsync(stream); - Logger.Log($"OUT MSG: {_blockString}", LogLevel.Critical, this); - //log message sent - ASCIIEncoding enc = new ASCIIEncoding(); - string characters = enc.GetString(message); - } - } catch (IOException) { - Logger.Log($"Critical IO exception on send", LogLevel.Critical, this); - return null; - } catch (SocketException) { - OnMajorSocketException(); - return null; - } - - //await result - StringBuilder response = new StringBuilder(); - try { - byte[] responseBuffer = new byte[256]; - do { - int bytes = stream.Read(responseBuffer, 0, responseBuffer.Length); - response.Append(Encoding.UTF8.GetString(responseBuffer, 0, bytes)); - } - while (stream.DataAvailable); - } catch (IOException) { - Logger.Log($"Critical IO exception on receive", LogLevel.Critical, this); - return null; - } catch (SocketException) { - OnMajorSocketException(); - return null; - } - - sw.Stop(); - var curCycle = (int)sw.ElapsedMilliseconds; - if (Math.Abs(CycleTimeMs - curCycle) > 2) { - CycleTimeMs = curCycle; - } - - Logger.Log($"IN MSG ({(int)sw.Elapsed.TotalMilliseconds}ms): {response}", LogLevel.Critical, this); - return response.ToString(); - } - - } catch (SocketException) { - - OnMajorSocketException(); + if (client == null || !client.Connected ) { + await ConnectTCP(); + if (!client.Connected) return null; + } + + var message = _blockString.ToHexASCIIBytes(); + + //send request + using (var sendStream = new MemoryStream(message)) { + await sendStream.CopyToAsync(stream); + Logger.Log($"[--------------------------------]", LogLevel.Critical, this); + Logger.Log($"--> OUT MSG: {_blockString}", LogLevel.Critical, this); + } + + //await result + StringBuilder response = new StringBuilder(); + try { + + byte[] responseBuffer = new byte[128 * 16]; + + bool endLineCode = false; + bool startMsgCode = false; + + while (!endLineCode && !startMsgCode) { + + do { + int bytes = await stream.ReadAsync(responseBuffer, 0, responseBuffer.Length); + + endLineCode = responseBuffer.Any(x => x == 0x0D); + startMsgCode = responseBuffer.Count(x => x == 0x25) > 1; + + if (!endLineCode && !startMsgCode) break; + + response.Append(Encoding.UTF8.GetString(responseBuffer, 0, bytes)); + } + while (stream.DataAvailable); } + } catch (IOException) { + Logger.Log($"Critical IO exception on receive", LogLevel.Critical, this); + return null; + } catch (SocketException) { + OnMajorSocketException(); + return null; + } + + if(!string.IsNullOrEmpty(response.ToString())) { + Logger.Log($"<-- IN MSG (TXT): {response} ({response.Length} bytes)", LogLevel.Critical, this); + return response.ToString(); + } else { + return null; } } @@ -709,4 +712,5 @@ namespace MewtocolNet { } + } \ No newline at end of file diff --git a/MewtocolNet/Mewtocol/MewtocolInterfaceRequests.cs b/MewtocolNet/Mewtocol/MewtocolInterfaceRequests.cs index 01e8a3d..02061a4 100644 --- a/MewtocolNet/Mewtocol/MewtocolInterfaceRequests.cs +++ b/MewtocolNet/Mewtocol/MewtocolInterfaceRequests.cs @@ -117,7 +117,6 @@ namespace MewtocolNet { string startStr = start.ToString().PadLeft(5, '0'); var wordLength = count / 2; - bool wasOdd = false; if (count % 2 != 0) wordLength++; @@ -128,8 +127,13 @@ namespace MewtocolNet { if(result.Success && !string.IsNullOrEmpty(result.Response)) { + var res = result; + var bytes = result.Response.ParseDTByteString(wordLength * 4).HexStringToByteArray(); + if (bytes == null) + return null; + return bytes.BigToMixedEndian().Take(count).ToArray(); } @@ -196,7 +200,6 @@ namespace MewtocolNet { /// /// Type of number (short, ushort, int, uint, float) /// The register to read - /// Station number to access /// A result with the given NumberRegister containing the readback value and a result struct public async Task> ReadNumRegister (NRegister _toRead) { @@ -205,40 +208,47 @@ namespace MewtocolNet { string requeststring = $"%{GetStationNumber()}#RD{_toRead.BuildMewtocolIdent()}"; var result = await SendCommandAsync(requeststring); - if(!result.Success || string.IsNullOrEmpty(result.Response)) { - return new NRegisterResult { - Result = result, - Register = _toRead - }; + var failedResult = new NRegisterResult { + Result = result, + Register = _toRead + }; + + if (!result.Success || string.IsNullOrEmpty(result.Response)) { + return failedResult; } if (numType == typeof(short)) { var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder(); + if (resultBytes == null) return failedResult; var val = short.Parse(resultBytes, NumberStyles.HexNumber); _toRead.SetValueFromPLC(val); } else if (numType == typeof(ushort)) { var resultBytes = result.Response.ParseDTByteString(4).ReverseByteOrder(); + if (resultBytes == null) return failedResult; var val = ushort.Parse(resultBytes, NumberStyles.HexNumber); _toRead.SetValueFromPLC(val); } else if (numType == typeof(int)) { var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder(); + if (resultBytes == null) return failedResult; var val = int.Parse(resultBytes, NumberStyles.HexNumber); _toRead.SetValueFromPLC(val); } else if (numType == typeof(uint)) { var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder(); + if (resultBytes == null) return failedResult; var val = uint.Parse(resultBytes, NumberStyles.HexNumber); _toRead.SetValueFromPLC(val); } else if (numType == typeof(float)) { var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder(); + if (resultBytes == null) return failedResult; //convert to unsigned int first var val = uint.Parse(resultBytes, NumberStyles.HexNumber); @@ -250,6 +260,7 @@ namespace MewtocolNet { } else if (numType == typeof(TimeSpan)) { var resultBytes = result.Response.ParseDTByteString(8).ReverseByteOrder(); + if (resultBytes == null) return failedResult; //convert to unsigned int first var vallong = long.Parse(resultBytes, NumberStyles.HexNumber); var valMillis = vallong * 10; diff --git a/MewtocolNet/Mewtocol/Subregisters/Register.cs b/MewtocolNet/Mewtocol/Subregisters/Register.cs index a685b87..eebf7e1 100644 --- a/MewtocolNet/Mewtocol/Subregisters/Register.cs +++ b/MewtocolNet/Mewtocol/Subregisters/Register.cs @@ -145,7 +145,7 @@ namespace MewtocolNet.Registers { return boolReg.Value.ToString(); } if (this is SRegister stringReg) { - return stringReg.Value.ToString(); + return stringReg.Value ?? ""; } return "Type of the register is not supported."; diff --git a/MewtocolNet/MewtocolNet.csproj b/MewtocolNet/MewtocolNet.csproj index 86110db..bbe36d7 100644 --- a/MewtocolNet/MewtocolNet.csproj +++ b/MewtocolNet/MewtocolNet.csproj @@ -2,7 +2,7 @@ netstandard2.0 MewtocolNet - 0.4.3 + 0.5.0 Felix Weiss Womed true diff --git a/MewtocolNet/Queue/SerialQueue.cs b/MewtocolNet/Queue/SerialQueue.cs new file mode 100644 index 0000000..a1faf22 --- /dev/null +++ b/MewtocolNet/Queue/SerialQueue.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; + +namespace MewtocolNet.Queue { + + internal class SerialQueue { + + readonly object _locker = new object(); + readonly WeakReference _lastTask = new WeakReference(null); + + internal Task Enqueue (Action action) { + return Enqueue(() => { + action(); + return true; + }); + } + + internal Task Enqueue (Func function) { + lock (_locker) { + Task lastTask; + Task resultTask; + + if (_lastTask.TryGetTarget(out lastTask)) { + resultTask = lastTask.ContinueWith(_ => function(), TaskContinuationOptions.ExecuteSynchronously); + } else { + resultTask = Task.Run(function); + } + + _lastTask.SetTarget(resultTask); + + return resultTask; + } + } + + internal Task Enqueue (Func asyncAction) { + lock (_locker) { + Task lastTask; + Task resultTask; + + if (_lastTask.TryGetTarget(out lastTask)) { + resultTask = lastTask.ContinueWith(_ => asyncAction(), TaskContinuationOptions.ExecuteSynchronously).Unwrap(); + } else { + resultTask = Task.Run(asyncAction); + } + + _lastTask.SetTarget(resultTask); + + return resultTask; + } + } + + internal Task Enqueue (Func> asyncFunction) { + lock (_locker) { + Task lastTask; + Task resultTask; + + if (_lastTask.TryGetTarget(out lastTask)) { + resultTask = lastTask.ContinueWith(_ => asyncFunction(), TaskContinuationOptions.ExecuteSynchronously).Unwrap(); + } else { + resultTask = Task.Run(asyncFunction); + } + + _lastTask.SetTarget(resultTask); + + return resultTask; + } + } + + } + +}