Skip to content

Commit 241a9d1

Browse files
Async optimization
1 parent 9283b04 commit 241a9d1

File tree

8 files changed

+133
-104
lines changed

8 files changed

+133
-104
lines changed

AMWD.Protocols.Modbus.Common/Contracts/ModbusClientBase.cs

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,27 @@ namespace AMWD.Protocols.Modbus.Common.Contracts
1010
/// <summary>
1111
/// Base implementation of a Modbus client.
1212
/// </summary>
13-
public abstract class ModbusClientBase : IDisposable
13+
/// <remarks>
14+
/// Initializes a new instance of the <see cref="ModbusClientBase"/> class with a specific <see cref="IModbusConnection"/>.
15+
/// </remarks>
16+
/// <param name="connection">The <see cref="IModbusConnection"/> responsible for invoking the requests.</param>
17+
/// <param name="disposeConnection">
18+
/// <see langword="true"/> if the connection should be disposed of by Dispose(),
19+
/// <see langword="false"/> otherwise if you inted to reuse the connection.
20+
/// </param>
21+
public abstract class ModbusClientBase(IModbusConnection connection, bool disposeConnection) : IDisposable
1422
{
1523
private bool _isDisposed;
1624

1725
/// <summary>
1826
/// Gets or sets a value indicating whether the connection should be disposed of by <see cref="Dispose()"/>.
1927
/// </summary>
20-
protected readonly bool disposeConnection;
28+
protected readonly bool disposeConnection = disposeConnection;
2129

2230
/// <summary>
2331
/// Gets or sets the <see cref="IModbusConnection"/> responsible for invoking the requests.
2432
/// </summary>
25-
protected readonly IModbusConnection connection;
33+
protected readonly IModbusConnection connection = connection ?? throw new ArgumentNullException(nameof(connection));
2634

2735
/// <summary>
2836
/// Initializes a new instance of the <see cref="ModbusClientBase"/> class with a specific <see cref="IModbusConnection"/>.
@@ -32,20 +40,6 @@ public ModbusClientBase(IModbusConnection connection)
3240
: this(connection, true)
3341
{ }
3442

35-
/// <summary>
36-
/// Initializes a new instance of the <see cref="ModbusClientBase"/> class with a specific <see cref="IModbusConnection"/>.
37-
/// </summary>
38-
/// <param name="connection">The <see cref="IModbusConnection"/> responsible for invoking the requests.</param>
39-
/// <param name="disposeConnection">
40-
/// <see langword="true"/> if the connection should be disposed of by Dispose(),
41-
/// <see langword="false"/> otherwise if you inted to reuse the connection.
42-
/// </param>
43-
public ModbusClientBase(IModbusConnection connection, bool disposeConnection)
44-
{
45-
this.connection = connection ?? throw new ArgumentNullException(nameof(connection));
46-
this.disposeConnection = disposeConnection;
47-
}
48-
4943
/// <summary>
5044
/// Gets or sets the protocol type to use.
5145
/// </summary>
@@ -67,7 +61,7 @@ public virtual async Task<IReadOnlyList<Coil>> ReadCoilsAsync(byte unitId, ushor
6761
Assertions();
6862

6963
var request = Protocol.SerializeReadCoils(unitId, startAddress, count);
70-
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken);
64+
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
7165
Protocol.ValidateResponse(request, response);
7266

7367
// The protocol processes complete bytes from the response.
@@ -92,7 +86,7 @@ public virtual async Task<IReadOnlyList<DiscreteInput>> ReadDiscreteInputsAsync(
9286
Assertions();
9387

9488
var request = Protocol.SerializeReadDiscreteInputs(unitId, startAddress, count);
95-
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken);
89+
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
9690
Protocol.ValidateResponse(request, response);
9791

9892
// The protocol processes complete bytes from the response.
@@ -117,7 +111,7 @@ public virtual async Task<IReadOnlyList<HoldingRegister>> ReadHoldingRegistersAs
117111
Assertions();
118112

119113
var request = Protocol.SerializeReadHoldingRegisters(unitId, startAddress, count);
120-
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken);
114+
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
121115
Protocol.ValidateResponse(request, response);
122116

123117
var holdingRegisters = Protocol.DeserializeReadHoldingRegisters(response).ToList();
@@ -140,7 +134,7 @@ public virtual async Task<IReadOnlyList<InputRegister>> ReadInputRegistersAsync(
140134
Assertions();
141135

142136
var request = Protocol.SerializeReadInputRegisters(unitId, startAddress, count);
143-
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken);
137+
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
144138
Protocol.ValidateResponse(request, response);
145139

146140
var inputRegisters = Protocol.DeserializeReadInputRegisters(response).ToList();
@@ -184,7 +178,7 @@ public virtual async Task<DeviceIdentification> ReadDeviceIdentificationAsync(by
184178
do
185179
{
186180
var request = Protocol.SerializeReadDeviceIdentification(unitId, category, requestObjectId);
187-
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken);
181+
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
188182
Protocol.ValidateResponse(request, response);
189183

190184
result = Protocol.DeserializeReadDeviceIdentification(response);
@@ -247,7 +241,7 @@ public virtual async Task<bool> WriteSingleCoilAsync(byte unitId, Coil coil, Can
247241
Assertions();
248242

249243
var request = Protocol.SerializeWriteSingleCoil(unitId, coil);
250-
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken);
244+
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
251245
Protocol.ValidateResponse(request, response);
252246

253247
var result = Protocol.DeserializeWriteSingleCoil(response);
@@ -268,7 +262,7 @@ public virtual async Task<bool> WriteSingleHoldingRegisterAsync(byte unitId, Hol
268262
Assertions();
269263

270264
var request = Protocol.SerializeWriteSingleHoldingRegister(unitId, register);
271-
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken);
265+
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
272266
Protocol.ValidateResponse(request, response);
273267

274268
var result = Protocol.DeserializeWriteSingleHoldingRegister(response);
@@ -289,7 +283,7 @@ public virtual async Task<bool> WriteMultipleCoilsAsync(byte unitId, IReadOnlyLi
289283
Assertions();
290284

291285
var request = Protocol.SerializeWriteMultipleCoils(unitId, coils);
292-
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken);
286+
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
293287
Protocol.ValidateResponse(request, response);
294288

295289
var (firstAddress, count) = Protocol.DeserializeWriteMultipleCoils(response);
@@ -309,7 +303,7 @@ public virtual async Task<bool> WriteMultipleHoldingRegistersAsync(byte unitId,
309303
Assertions();
310304

311305
var request = Protocol.SerializeWriteMultipleHoldingRegisters(unitId, registers);
312-
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken);
306+
var response = await connection.InvokeAsync(request, Protocol.CheckResponseComplete, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
313307
Protocol.ValidateResponse(request, response);
314308

315309
var (firstAddress, count) = Protocol.DeserializeWriteMultipleHoldingRegisters(response);

AMWD.Protocols.Modbus.Serial/ModbusRtuProxy.cs

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -192,17 +192,16 @@ public Task StartAsync(CancellationToken cancellationToken = default)
192192
public Task StopAsync(CancellationToken cancellationToken = default)
193193
{
194194
Assertions();
195-
return StopAsyncInternal(cancellationToken);
195+
StopAsyncInternal();
196+
return Task.CompletedTask;
196197
}
197198

198-
private Task StopAsyncInternal(CancellationToken cancellationToken)
199+
private void StopAsyncInternal()
199200
{
200201
_stopCts?.Cancel();
201202

202203
_serialPort.Close();
203204
_serialPort.DataReceived -= OnDataReceived;
204-
205-
return Task.CompletedTask;
206205
}
207206

208207
/// <summary>
@@ -215,7 +214,7 @@ public void Dispose()
215214

216215
_isDisposed = true;
217216

218-
StopAsyncInternal(CancellationToken.None).Wait();
217+
StopAsyncInternal();
219218

220219
_serialPort.Dispose();
221220
_stopCts?.Dispose();
@@ -332,7 +331,7 @@ private async Task<byte[]> HandleReadCoilsAsync(byte[] requestBytes, Cancellatio
332331
responseBytes.AddRange(requestBytes.Take(2));
333332
try
334333
{
335-
var coils = await Client.ReadCoilsAsync(unitId, firstAddress, count, cancellationToken);
334+
var coils = await Client.ReadCoilsAsync(unitId, firstAddress, count, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
336335

337336
byte[] values = new byte[(int)Math.Ceiling(coils.Count / 8.0)];
338337
for (int i = 0; i < coils.Count; i++)
@@ -371,7 +370,7 @@ private async Task<byte[]> HandleReadDiscreteInputsAsync(byte[] requestBytes, Ca
371370
responseBytes.AddRange(requestBytes.Take(2));
372371
try
373372
{
374-
var discreteInputs = await Client.ReadDiscreteInputsAsync(unitId, firstAddress, count, cancellationToken);
373+
var discreteInputs = await Client.ReadDiscreteInputsAsync(unitId, firstAddress, count, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
375374

376375
byte[] values = new byte[(int)Math.Ceiling(discreteInputs.Count / 8.0)];
377376
for (int i = 0; i < discreteInputs.Count; i++)
@@ -410,7 +409,7 @@ private async Task<byte[]> HandleReadHoldingRegistersAsync(byte[] requestBytes,
410409
responseBytes.AddRange(requestBytes.Take(2));
411410
try
412411
{
413-
var holdingRegisters = await Client.ReadHoldingRegistersAsync(unitId, firstAddress, count, cancellationToken);
412+
var holdingRegisters = await Client.ReadHoldingRegistersAsync(unitId, firstAddress, count, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
414413

415414
byte[] values = new byte[holdingRegisters.Count * 2];
416415
for (int i = 0; i < holdingRegisters.Count; i++)
@@ -444,7 +443,7 @@ private async Task<byte[]> HandleReadInputRegistersAsync(byte[] requestBytes, Ca
444443
responseBytes.AddRange(requestBytes.Take(2));
445444
try
446445
{
447-
var inputRegisters = await Client.ReadInputRegistersAsync(unitId, firstAddress, count, cancellationToken);
446+
var inputRegisters = await Client.ReadInputRegistersAsync(unitId, firstAddress, count, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
448447

449448
byte[] values = new byte[count * 2];
450449
for (int i = 0; i < count; i++)
@@ -492,7 +491,7 @@ private async Task<byte[]> HandleWriteSingleCoilAsync(byte[] requestBytes, Cance
492491
LowByte = requestBytes[5],
493492
};
494493

495-
bool isSuccess = await Client.WriteSingleCoilAsync(requestBytes[0], coil, cancellationToken);
494+
bool isSuccess = await Client.WriteSingleCoilAsync(requestBytes[0], coil, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
496495
if (isSuccess)
497496
{
498497
// Response is an echo of the request
@@ -531,7 +530,7 @@ private async Task<byte[]> HandleWriteSingleRegisterAsync(byte[] requestBytes, C
531530
LowByte = requestBytes[5]
532531
};
533532

534-
bool isSuccess = await Client.WriteSingleHoldingRegisterAsync(requestBytes[0], register, cancellationToken);
533+
bool isSuccess = await Client.WriteSingleHoldingRegisterAsync(requestBytes[0], register, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
535534
if (isSuccess)
536535
{
537536
// Response is an echo of the request
@@ -591,7 +590,7 @@ private async Task<byte[]> HandleWriteMultipleCoilsAsync(byte[] requestBytes, Ca
591590
});
592591
}
593592

594-
bool isSuccess = await Client.WriteMultipleCoilsAsync(requestBytes[0], coils, cancellationToken);
593+
bool isSuccess = await Client.WriteMultipleCoilsAsync(requestBytes[0], coils, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
595594
if (isSuccess)
596595
{
597596
// Response is an echo of the request
@@ -648,7 +647,7 @@ private async Task<byte[]> HandleWriteMultipleRegistersAsync(byte[] requestBytes
648647
});
649648
}
650649

651-
bool isSuccess = await Client.WriteMultipleHoldingRegistersAsync(requestBytes[0], list, cancellationToken);
650+
bool isSuccess = await Client.WriteMultipleHoldingRegistersAsync(requestBytes[0], list, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
652651
if (isSuccess)
653652
{
654653
// Response is an echo of the request
@@ -705,7 +704,7 @@ private async Task<byte[]> HandleEncapsulatedInterfaceAsync(byte[] requestBytes,
705704

706705
try
707706
{
708-
var deviceInfo = await Client.ReadDeviceIdentificationAsync(requestBytes[0], category, firstObject, cancellationToken);
707+
var deviceInfo = await Client.ReadDeviceIdentificationAsync(requestBytes[0], category, firstObject, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
709708

710709
var bodyBytes = new List<byte>();
711710

@@ -855,5 +854,21 @@ private static byte[] ReturnResponse(List<byte> response)
855854
}
856855

857856
#endregion Request Handling
857+
858+
/// <inheritdoc/>
859+
public override string ToString()
860+
{
861+
var sb = new StringBuilder();
862+
863+
sb.AppendLine($"RTU Proxy");
864+
sb.AppendLine($" {nameof(PortName)}: {PortName}");
865+
sb.AppendLine($" {nameof(BaudRate)}: {(int)BaudRate}");
866+
sb.AppendLine($" {nameof(DataBits)}: {DataBits}");
867+
sb.AppendLine($" {nameof(StopBits)}: {StopBits}");
868+
sb.AppendLine($" {nameof(Parity)}: {Parity}");
869+
sb.AppendLine($" {nameof(Client)}: {Client.GetType().Name}");
870+
871+
return sb.ToString();
872+
}
858873
}
859874
}

AMWD.Protocols.Modbus.Serial/ModbusSerialConnection.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ public class ModbusSerialConnection : IModbusConnection
3131
private readonly Task _processingTask;
3232
private readonly AsyncQueue<RequestQueueItem> _requestQueue = new();
3333

34-
// Only required to cover all logic branches on unit tests.
35-
private bool _isUnitTest = false;
34+
private readonly bool _isLinux;
3635

3736
#endregion Fields
3837

@@ -41,6 +40,8 @@ public class ModbusSerialConnection : IModbusConnection
4140
/// </summary>
4241
public ModbusSerialConnection(string portName)
4342
{
43+
_isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
44+
4445
if (string.IsNullOrWhiteSpace(portName))
4546
throw new ArgumentNullException(nameof(portName));
4647

@@ -268,21 +269,21 @@ private async Task ProcessAsync(CancellationToken cancellationToken)
268269
try
269270
{
270271
// Get next request to process
271-
var item = await _requestQueue.DequeueAsync(cancellationToken);
272+
var item = await _requestQueue.DequeueAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
272273

273274
// Remove registration => already removed from queue
274275
item.CancellationTokenRegistration.Dispose();
275276

276277
// Build combined cancellation token
277278
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, item.CancellationTokenSource.Token);
278279
// Wait for exclusive access
279-
await _portLock.WaitAsync(linkedCts.Token);
280+
await _portLock.WaitAsync(linkedCts.Token).ConfigureAwait(continueOnCapturedContext: false);
280281
try
281282
{
282283
// Ensure connection is up
283284
await AssertConnection(linkedCts.Token);
284285

285-
await _serialPort.WriteAsync(item.Request, linkedCts.Token);
286+
await _serialPort.WriteAsync(item.Request, linkedCts.Token).ConfigureAwait(continueOnCapturedContext: false);
286287

287288
linkedCts.Token.ThrowIfCancellationRequested();
288289

@@ -291,7 +292,7 @@ private async Task ProcessAsync(CancellationToken cancellationToken)
291292

292293
do
293294
{
294-
int readCount = await _serialPort.ReadAsync(buffer, 0, buffer.Length, linkedCts.Token);
295+
int readCount = await _serialPort.ReadAsync(buffer, 0, buffer.Length, linkedCts.Token).ConfigureAwait(continueOnCapturedContext: false);
295296
if (readCount < 1)
296297
throw new EndOfStreamException();
297298

@@ -322,7 +323,7 @@ private async Task ProcessAsync(CancellationToken cancellationToken)
322323
_portLock.Release();
323324
_idleTimer.Change(IdleTimeout, Timeout.InfiniteTimeSpan);
324325

325-
await Task.Delay(InterRequestDelay, cancellationToken);
326+
await Task.Delay(InterRequestDelay, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
326327
}
327328
}
328329
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
@@ -353,15 +354,15 @@ private async Task AssertConnection(CancellationToken cancellationToken)
353354
_serialPort.Close();
354355
_serialPort.ResetRS485DriverStateFlags();
355356

356-
if (DriverEnabledRS485 && (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || _isUnitTest))
357+
if (DriverEnabledRS485 && _isLinux)
357358
{
358359
var flags = _serialPort.GetRS485DriverStateFlags();
359360
flags |= RS485Flags.Enabled;
360361
flags &= ~RS485Flags.RxDuringTx;
361362
_serialPort.ChangeRS485DriverStateFlags(flags);
362363
}
363364

364-
using var connectTask = Task.Run(_serialPort.Open);
365+
using var connectTask = Task.Run(_serialPort.Open, cancellationToken);
365366
if (await Task.WhenAny(connectTask, Task.Delay(ReadTimeout, cancellationToken)) == connectTask)
366367
{
367368
await connectTask;
@@ -379,7 +380,7 @@ private async Task AssertConnection(CancellationToken cancellationToken)
379380

380381
try
381382
{
382-
await Task.Delay(TimeSpan.FromSeconds(delay), cancellationToken);
383+
await Task.Delay(TimeSpan.FromSeconds(delay), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
383384
}
384385
catch
385386
{ /* keep it quiet */ }

AMWD.Protocols.Modbus.Tcp/Extensions/StreamExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public static async Task<byte[]> ReadExpectedBytesAsync(this Stream stream, int
1212
int offset = 0;
1313
do
1414
{
15-
int count = await stream.ReadAsync(buffer, offset, expectedBytes - offset, cancellationToken);
15+
int count = await stream.ReadAsync(buffer, offset, expectedBytes - offset, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
1616
if (count < 1)
1717
throw new EndOfStreamException();
1818

@@ -30,7 +30,7 @@ public static async Task<byte[]> ReadExpectedBytesAsync(this NetworkStreamWrappe
3030
int offset = 0;
3131
do
3232
{
33-
int count = await stream.ReadAsync(buffer, offset, expectedBytes - offset, cancellationToken);
33+
int count = await stream.ReadAsync(buffer, offset, expectedBytes - offset, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
3434
if (count < 1)
3535
throw new EndOfStreamException();
3636

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Threading.Tasks;
2+
3+
namespace AMWD.Protocols.Modbus.Tcp.Extensions
4+
{
5+
internal static class TaskExtensions
6+
{
7+
public static async void Forget(this Task task)
8+
{
9+
try
10+
{
11+
await task;
12+
}
13+
catch
14+
{ /* keep it quiet */ }
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)