diff --git a/S7.Net.UnitTest/ProtocolTests.cs b/S7.Net.UnitTest/ProtocolTests.cs
index fdb68d9..4d53f17 100644
--- a/S7.Net.UnitTest/ProtocolTests.cs
+++ b/S7.Net.UnitTest/ProtocolTests.cs
@@ -13,15 +13,17 @@ namespace S7.Net.UnitTest
[TestClass]
public class ProtocolUnitTest
{
+ private TestContext TestContext { get; set; }
+
[TestMethod]
- public void TPKT_Read()
+ public async Task TPKT_Read()
{
var m = new MemoryStream(StringToByteArray("0300002902f0803203000000010002001400000401ff0400807710000100000103000000033f8ccccd"));
var t = TPKT.Read(m);
Assert.AreEqual(0x03, t.Version);
Assert.AreEqual(0x29, t.Length);
m.Position = 0;
- t = TPKT.ReadAsync(m).Result;
+ t = await TPKT.ReadAsync(m, TestContext.CancellationTokenSource.Token);
Assert.AreEqual(0x03, t.Version);
Assert.AreEqual(0x29, t.Length);
}
@@ -40,7 +42,7 @@ namespace S7.Net.UnitTest
public async Task TPKT_ReadShortAsync()
{
var m = new MemoryStream(StringToByteArray("0300002902f0803203000000010002001400000401ff040080"));
- var t = await TPKT.ReadAsync(m);
+ var t = await TPKT.ReadAsync(m, TestContext.CancellationTokenSource.Token);
}
[TestMethod]
@@ -51,7 +53,7 @@ namespace S7.Net.UnitTest
var t = COTP.TSDU.Read(m);
Assert.IsTrue(expected.SequenceEqual(t));
m.Position = 0;
- t = COTP.TSDU.ReadAsync(m).Result;
+ t = COTP.TSDU.ReadAsync(m, TestContext.CancellationTokenSource.Token).Result;
Assert.IsTrue(expected.SequenceEqual(t));
}
diff --git a/S7.Net.UnitTest/S7NetTestsAsync.cs b/S7.Net.UnitTest/S7NetTestsAsync.cs
index 7269199..604e2bb 100644
--- a/S7.Net.UnitTest/S7NetTestsAsync.cs
+++ b/S7.Net.UnitTest/S7NetTestsAsync.cs
@@ -10,6 +10,7 @@ using System.ServiceProcess;
using S7.Net.Types;
using S7.UnitTest.Helpers;
using System.Threading.Tasks;
+using System.Threading;
#endregion
@@ -908,6 +909,42 @@ namespace S7.Net.UnitTest
Assert.AreEqual(x % 256, res[x], string.Format("Bit {0} failed", x));
}
}
+
+ ///
+ /// Write a large amount of data and test cancellation
+ ///
+ [TestMethod]
+ public async Task Test_Async_WriteLargeByteArrayWithCancellation()
+ {
+ Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
+
+ var cancellationSource = new CancellationTokenSource();
+ var cancellationToken = cancellationSource.Token;
+
+ var randomEngine = new Random();
+ var data = new byte[8192];
+ var db = 2;
+ randomEngine.NextBytes(data);
+
+ cancellationSource.CancelAfter(TimeSpan.FromMilliseconds(5));
+ try
+ {
+ await plc.WriteBytesAsync(DataType.DataBlock, db, 0, data, cancellationToken);
+ }
+ catch(TaskCanceledException)
+ {
+ // everything is good, that is the exception we expect
+ Console.WriteLine("Task was cancelled as expected.");
+ return;
+ }
+ catch(Exception e)
+ {
+ Assert.Fail($"Wrong exception type received. Expected {typeof(TaskCanceledException)}, received {e.GetType()}.");
+ }
+
+ // Depending on how tests run, this can also just succeed without getting cancelled at all. Do nothing in this case.
+ Console.WriteLine("Task was not cancelled as expected.");
+ }
#endregion
}
}
diff --git a/S7.Net.UnitTest/StreamTests.cs b/S7.Net.UnitTest/StreamTests.cs
index 8bb0564..375c02c 100644
--- a/S7.Net.UnitTest/StreamTests.cs
+++ b/S7.Net.UnitTest/StreamTests.cs
@@ -65,13 +65,14 @@ namespace S7.Net.UnitTest
[TestClass]
public class StreamTests
{
+ private TestContext TestContext { get; set; }
[TestMethod]
public async Task TPKT_ReadRestrictedStreamAsync()
{
var fullMessage = ProtocolUnitTest.StringToByteArray("0300002902f0803203000000010002001400000401ff0400807710000100000103000000033f8ccccd");
var m = new TestStream1BytePerRead(fullMessage);
- var t = await TPKT.ReadAsync(m);
+ var t = await TPKT.ReadAsync(m, TestContext.CancellationTokenSource.Token);
Assert.AreEqual(fullMessage.Length, t.Length);
Assert.AreEqual(fullMessage.Last(), t.Data.Last());
}
diff --git a/S7.Net/COTP.cs b/S7.Net/COTP.cs
index c4af73a..2451893 100644
--- a/S7.Net/COTP.cs
+++ b/S7.Net/COTP.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Threading;
using System.Threading.Tasks;
namespace S7.Net
@@ -66,9 +67,9 @@ namespace S7.Net
///
/// The socket to read from
/// COTP DPDU instance
- public static async Task ReadAsync(Stream stream)
+ public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken)
{
- var tpkt = await TPKT.ReadAsync(stream);
+ var tpkt = await TPKT.ReadAsync(stream, cancellationToken);
if (tpkt.Length == 0)
{
throw new TPDUInvalidException("No protocol data received");
@@ -130,9 +131,9 @@ namespace S7.Net
///
/// The stream to read from
/// Data in TSDU
- public static async Task ReadAsync(Stream stream)
+ public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken)
{
- var segment = await TPDU.ReadAsync(stream);
+ var segment = await TPDU.ReadAsync(stream, cancellationToken);
if (segment.LastDataUnit)
{
@@ -145,7 +146,7 @@ namespace S7.Net
while (!segment.LastDataUnit)
{
- segment = await TPDU.ReadAsync(stream);
+ segment = await TPDU.ReadAsync(stream, cancellationToken);
var previousLength = buffer.Length;
Array.Resize(ref buffer, buffer.Length + segment.Data.Length);
Array.Copy(segment.Data, 0, buffer, previousLength, segment.Data.Length);
diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs
index 2f481e4..1de0ac8 100644
--- a/S7.Net/PlcAsynchronous.cs
+++ b/S7.Net/PlcAsynchronous.cs
@@ -6,6 +6,7 @@ using System.Net.Sockets;
using System.Threading.Tasks;
using S7.Net.Protocol;
using System.IO;
+using System.Threading;
namespace S7.Net
{
@@ -17,14 +18,18 @@ namespace S7.Net
///
/// Connects to the PLC and performs a COTP ConnectionRequest and S7 CommunicationSetup.
///
+ /// The token to monitor for cancellation requests. The default value is None.
+ /// Please note that the cancellation will not affect opening the socket in any way and only affects data transfers for configuring the connection after the socket connection is successfully established.
+ /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.
/// A task that represents the asynchronous open operation.
- public async Task OpenAsync()
+ public async Task OpenAsync(CancellationToken cancellationToken = default)
{
await ConnectAsync();
+ cancellationToken.ThrowIfCancellationRequested();
var stream = GetStreamIfAvailable();
-
+
await stream.WriteAsync(ConnectionRequest.GetCOTPConnectionRequest(CPU, Rack, Slot), 0, 22);
- var response = await COTP.TPDU.ReadAsync(stream);
+ var response = await COTP.TPDU.ReadAsync(stream, cancellationToken);
if (response == null)
{
throw new Exception("Error reading Connection Confirm. Malformed TPDU packet");
@@ -36,7 +41,7 @@ namespace S7.Net
await stream.WriteAsync(GetS7ConnectionSetup(), 0, 25);
- var s7data = await COTP.TSDU.ReadAsync(stream);
+ var s7data = await COTP.TSDU.ReadAsync(stream, cancellationToken);
if (s7data == null)
throw new WrongNumberOfBytesException("No data received in response to Communication Setup");
if (s7data.Length < 2)
@@ -69,8 +74,10 @@ namespace S7.Net
/// Address of the memory area (if you want to read DB1, this is set to 1). This must be set also for other memory area types: counters, timers,etc.
/// Start byte address. If you want to read DB1.DBW200, this is 200.
/// Byte count, if you want to read 120 bytes, set this to 120.
+ /// The token to monitor for cancellation requests. The default value is None.
+ /// Please note that cancellation is advisory/cooperative and will not lead to immediate cancellation in all cases.
/// Returns the bytes in an array
- public async Task ReadBytesAsync(DataType dataType, int db, int startByteAdr, int count)
+ public async Task ReadBytesAsync(DataType dataType, int db, int startByteAdr, int count, CancellationToken cancellationToken = default)
{
var resultBytes = new byte[count];
int index = 0;
@@ -78,7 +85,7 @@ namespace S7.Net
{
//This works up to MaxPDUSize-1 on SNAP7. But not MaxPDUSize-0.
var maxToRead = Math.Min(count, MaxPDUSize - 18);
- await ReadBytesWithSingleRequestAsync(dataType, db, startByteAdr + index, resultBytes, index, maxToRead);
+ await ReadBytesWithSingleRequestAsync(dataType, db, startByteAdr + index, resultBytes, index, maxToRead, cancellationToken);
count -= maxToRead;
index += maxToRead;
}
@@ -96,10 +103,12 @@ namespace S7.Net
/// Type of the variable/s that you are reading
/// Address of bit. If you want to read DB1.DBX200.6, set 6 to this parameter.
///
- public async Task