diff --git a/src/Examples/VB.NET DotNetCore/VB.NET DotNetCore.vbproj b/src/Examples/VB.NET DotNetCore/VB.NET DotNetCore.vbproj index 59297548..e824a0d4 100644 --- a/src/Examples/VB.NET DotNetCore/VB.NET DotNetCore.vbproj +++ b/src/Examples/VB.NET DotNetCore/VB.NET DotNetCore.vbproj @@ -3,7 +3,7 @@ Exe VB.NET_DotNetCore - netcoreapp3.1 + netcoreapp3.1 diff --git a/src/libplctag.Tests/AsyncTests.cs b/src/libplctag.Tests/AsyncTests.cs index 92720a9a..a29eb2d1 100644 --- a/src/libplctag.Tests/AsyncTests.cs +++ b/src/libplctag.Tests/AsyncTests.cs @@ -65,7 +65,7 @@ public async Task Timeout_throws_a_LibPlcTagException() }; // Act - var ex = await Assert.ThrowsAsync(async () => { + var ex = await Assert.ThrowsAsync(async () => { await tag.InitializeAsync(); }); @@ -73,6 +73,58 @@ public async Task Timeout_throws_a_LibPlcTagException() Assert.Equal(Status.ErrorTimeout.ToString(), ex.Message); } + [Fact] + public async Task TryInitialize_returns_an_ErrorTimeout() + { + // Arrange + var nativeTag = new Mock(); + + nativeTag // The initial creation of the tag object returns a status, so we return pending + .Setup(m => m.plc_tag_create(It.IsAny(), 0)) + .Returns((int)Status.Pending); + + nativeTag // Subsequent calls to determine the tag status should still return pending + .Setup(m => m.plc_tag_status(It.IsAny())) + .Returns((int)Status.Pending); + + var tag = new NativeTagWrapper(nativeTag.Object) + { + Timeout = REALISTIC_TIMEOUT_FOR_ALL_OPERATIONS + }; + + // Act + var result = await tag.TryInitializeAsync(); + + // Assert + Assert.Equal(Status.ErrorTimeout, result); + } + + [Fact] + public async Task TryInitialize_Cancelled_cancellation_token_throws_a_TaskCanceledException() + { + // Arrange + var nativeTag = new Mock(); + + nativeTag // The initial creation of the tag object returns a status, so we return pending + .Setup(m => m.plc_tag_create(It.IsAny(), 0)) + .Returns((int)Status.Pending); + + nativeTag // Subsequent calls to determine the tag status should still return pending + .Setup(m => m.plc_tag_status(It.IsAny())) + .Returns((int)Status.Pending); + + var tag = new NativeTagWrapper(nativeTag.Object); + var cts = new CancellationTokenSource(); + + // Act + cts.CancelAfter(REALISTIC_TIMEOUT_FOR_ALL_OPERATIONS); + + // Assert + await Assert.ThrowsAsync(async () => { + await tag.TryInitializeAsync(cts.Token); + }); + } + [Fact] public async Task Timeout_returns_pending_but_eventually_ok() { diff --git a/src/libplctag/ITag.cs b/src/libplctag/ITag.cs index 5da8bb50..82a483fa 100644 --- a/src/libplctag/ITag.cs +++ b/src/libplctag/ITag.cs @@ -41,10 +41,18 @@ public interface ITag : IDisposable Status GetStatus(); void Initialize(); Task InitializeAsync(CancellationToken token = default); + bool TryInitialize(); + Task TryInitializeAsync(CancellationToken token = default); + object Read(); Task ReadAsync(CancellationToken token = default); + bool TryRead(); + Task TryReadAsync(CancellationToken token = default); + void Write(); Task WriteAsync(CancellationToken token = default); + bool TryWrite(); + Task TryWriteAsync(CancellationToken token = default); object Value { get; set; } } diff --git a/src/libplctag/NativeTagWrapper.cs b/src/libplctag/NativeTagWrapper.cs index 5cb7427a..500bebc3 100644 --- a/src/libplctag/NativeTagWrapper.cs +++ b/src/libplctag/NativeTagWrapper.cs @@ -309,7 +309,42 @@ public void Abort() public void Initialize() { + var status = TryInitialize(); + ThrowIfStatusNotOk(status); + } + + public async Task InitializeAsync(CancellationToken token = default) + { + var status = await TryInitializeAsync(token); + ThrowIfStatusNotOk(status); + } + + public void Read() + { + var status = TryRead(); + ThrowIfStatusNotOk(status); + } + + public async Task ReadAsync(CancellationToken token = default) + { + var status = await TryReadAsync(token); + ThrowIfStatusNotOk(status); + } + + public void Write() + { + var status = TryWrite(); + ThrowIfStatusNotOk(status); + } + + public async Task WriteAsync(CancellationToken token = default) + { + var status = await TryWriteAsync(token); + ThrowIfStatusNotOk(status); + } + public Status TryInitialize() + { ThrowIfAlreadyDisposed(); ThrowIfAlreadyInitialized(); @@ -321,17 +356,18 @@ public void Initialize() var result = _native.plc_tag_create_ex(attributeString, coreLibCallbackFuncExDelegate, IntPtr.Zero, millisecondTimeout); if (result < 0) - throw new LibPlcTagException((Status)result); + return (Status)result; else nativeTagHandle = result; _isInitialized = true; + + return (Status)result; } - public async Task InitializeAsync(CancellationToken token = default) + public async Task TryInitializeAsync(CancellationToken token = default) { - ThrowIfAlreadyDisposed(); ThrowIfAlreadyInitialized(); @@ -349,7 +385,7 @@ public async Task InitializeAsync(CancellationToken token = default) if (token.IsCancellationRequested) createTask.SetCanceled(); else - createTask.SetException(new LibPlcTagException(Status.ErrorTimeout)); + createTask.SetResult(Status.ErrorTimeout); } })) { @@ -369,25 +405,27 @@ public async Task InitializeAsync(CancellationToken token = default) if(GetStatus() == Status.Pending) await createTask.Task.ConfigureAwait(false); - ThrowIfStatusNotOk(createTask.Task.Result); - _isInitialized = true; + + var status = createTask.Task.Result; + return status; } } } - public void Read() + public Status TryRead() { ThrowIfAlreadyDisposed(); InitializeIfRequired(); var millisecondTimeout = (int)Timeout.TotalMilliseconds; - var result = (Status)_native.plc_tag_read(nativeTagHandle, millisecondTimeout); - ThrowIfStatusNotOk(result); + var status = (Status)_native.plc_tag_read(nativeTagHandle, millisecondTimeout); + + return status; } - public async Task ReadAsync(CancellationToken token = default) + public async Task TryReadAsync(CancellationToken token = default) { ThrowIfAlreadyDisposed(); await InitializeAsyncIfRequired(token).ConfigureAwait(false); @@ -405,31 +443,31 @@ public async Task ReadAsync(CancellationToken token = default) if (token.IsCancellationRequested) readTask.SetCanceled(); else - readTask.SetException(new LibPlcTagException(Status.ErrorTimeout)); + readTask.SetResult(Status.ErrorTimeout); } })) { var readTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); readTasks.Push(readTask); _native.plc_tag_read(nativeTagHandle, TIMEOUT_VALUE_THAT_INDICATES_ASYNC_OPERATION); - await readTask.Task.ConfigureAwait(false); - ThrowIfStatusNotOk(readTask.Task.Result); + var status = await readTask.Task.ConfigureAwait(false); + return status; } } } - public void Write() + public Status TryWrite() { ThrowIfAlreadyDisposed(); InitializeIfRequired(); var millisecondTimeout = (int)Timeout.TotalMilliseconds; - var result = (Status)_native.plc_tag_write(nativeTagHandle, millisecondTimeout); - ThrowIfStatusNotOk(result); + var status = (Status)_native.plc_tag_write(nativeTagHandle, millisecondTimeout); + return status; } - public async Task WriteAsync(CancellationToken token = default) + public async Task TryWriteAsync(CancellationToken token = default) { ThrowIfAlreadyDisposed(); await InitializeAsyncIfRequired(token).ConfigureAwait(false); @@ -447,15 +485,15 @@ public async Task WriteAsync(CancellationToken token = default) if (token.IsCancellationRequested) writeTask.SetCanceled(); else - writeTask.SetException(new LibPlcTagException(Status.ErrorTimeout)); + writeTask.SetResult(Status.ErrorTimeout); } })) { var writeTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); writeTasks.Push(writeTask); _native.plc_tag_write(nativeTagHandle, TIMEOUT_VALUE_THAT_INDICATES_ASYNC_OPERATION); - await writeTask.Task.ConfigureAwait(false); - ThrowIfStatusNotOk(writeTask.Task.Result); + var status = await writeTask.Task.ConfigureAwait(false); + return status; } } } @@ -638,15 +676,19 @@ private void ThrowIfAlreadyInitialized() throw new InvalidOperationException("Already initialized"); } + public bool IsStatusOk(Status? status = null) + { + var statusToCheck = status ?? GetStatus(); + return statusToCheck == Status.Ok; + } + private void ThrowIfStatusNotOk(Status? status = null) { var statusToCheck = status ?? GetStatus(); - if (statusToCheck != Status.Ok) + if (!IsStatusOk(status)) throw new LibPlcTagException(statusToCheck); } - - private void SetNativeTagValue(Func nativeMethod, int offset, T value) { ThrowIfAlreadyDisposed(); @@ -735,9 +777,6 @@ string FormatPlcType(PlcType? type) } - - - void SetUpEvents() { diff --git a/src/libplctag/Tag.cs b/src/libplctag/Tag.cs index 35bd9f55..324bca6e 100644 --- a/src/libplctag/Tag.cs +++ b/src/libplctag/Tag.cs @@ -327,6 +327,7 @@ public uint? StringTotalLength /// Can only be called once per instance. /// Timeout is controlled via class property. /// + /// public void Initialize() => _tag.Initialize(); /// @@ -384,6 +385,55 @@ public uint? StringTotalLength /// public Task WriteAsync(CancellationToken token = default) => _tag.WriteAsync(token); + /// + /// Creates the underlying data structures and references required before tag operations. + /// + /// + /// + /// Whether the operation was successful. + /// + /// + /// + /// Initializes the tag by establishing necessary connections. + /// Can only be called once per instance. + /// Timeout is controlled via class property. + /// + public bool TryInitialize() + { + var status = _tag.TryInitialize(); + return _tag.IsStatusOk(status); + } + + public async Task TryInitializeAsync(CancellationToken token = default) + { + var status = await _tag.TryInitializeAsync(token); + return _tag.IsStatusOk(status); + } + + public bool TryRead() + { + var status = _tag.TryRead(); + return _tag.IsStatusOk(status); + } + + public async Task TryReadAsync(CancellationToken token = default) + { + var status = await _tag.TryReadAsync(token); + return _tag.IsStatusOk(status); + } + + public bool TryWrite() + { + var status = _tag.TryWrite(); + return _tag.IsStatusOk(status); + } + + public async Task TryWriteAsync(CancellationToken token = default) + { + var status = await _tag.TryWriteAsync(token); + return _tag.IsStatusOk(status); + } + public void Abort() => _tag.Abort(); public void Dispose() => _tag.Dispose(); @@ -418,6 +468,8 @@ public uint? StringTotalLength /// Tag's current status public Status GetStatus() => _tag.GetStatus(); + public bool IsStatusOk(Status status) => _tag.IsStatusOk(status); + public bool GetBit(int offset) => _tag.GetBit(offset); public void SetBit(int offset, bool value) => _tag.SetBit(offset, value); diff --git a/src/libplctag/TagOfT.cs b/src/libplctag/TagOfT.cs index abd7df2a..f2b5d8ff 100644 --- a/src/libplctag/TagOfT.cs +++ b/src/libplctag/TagOfT.cs @@ -217,6 +217,115 @@ public void Write(T value) Write(); } + /// + public bool TryInitialize() + { + var isStatusOk = _tag.TryInitialize(); + if(!isStatusOk) + { + return false; + } + else + { + DecodeAll(); + return true; + } + } + + /// + public async Task TryInitializeAsync(CancellationToken token = default) + { + var isStatusOk = await _tag.TryInitializeAsync(token); + if (!isStatusOk) + { + return false; + } + else + { + DecodeAll(); + return true; + } + } + + /// + public async Task TryReadAsync(CancellationToken token = default) + { + var isStatusOk = await _tag.TryReadAsync(token); + if(!isStatusOk) + { + return false; + } + else + { + DecodeAll(); + return true; + } + } + + /// + public bool TryRead() + { + var isStatusOk = _tag.TryRead(); + if(!isStatusOk) + { + return false; + } + else + { + DecodeAll(); + return true; + } + } + + bool ITag.TryRead() => TryRead(); + + async Task ITag.TryReadAsync(CancellationToken token) => await TryReadAsync(token); + + /// + public async Task TryWriteAsync(CancellationToken token = default) + { + if (!_tag.IsInitialized) + { + if (!(await _tag.TryInitializeAsync(token))) + { + return false; + } + } + + EncodeAll(); + + return await _tag.TryWriteAsync(token); + } + + /// + public async Task TryWriteAsync(T value, CancellationToken token = default) + { + Value = value; + return await TryWriteAsync(token); + } + + /// + public bool TryWrite() + { + if (!_tag.IsInitialized) + { + if (!_tag.TryInitialize()) + { + return false; + } + } + + EncodeAll(); + return _tag.TryWrite(); + } + + /// + public bool TryWrite(T value) + { + Value = value; + return TryWrite(); + } + void DecodeAll() { Value = _plcMapper.Decode(_tag);