Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of Try-XYZ pattern #277

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Examples/VB.NET DotNetCore/VB.NET DotNetCore.vbproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>VB.NET_DotNetCore</RootNamespace>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand Down
54 changes: 53 additions & 1 deletion src/libplctag.Tests/AsyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,66 @@ public async Task Timeout_throws_a_LibPlcTagException()
};

// Act
var ex = await Assert.ThrowsAsync<LibPlcTagException>(async () => {
var ex = await Assert.ThrowsAsync<LibPlcTagException>(async () => {
await tag.InitializeAsync();
});

// Assert
Assert.Equal(Status.ErrorTimeout.ToString(), ex.Message);
}

[Fact]
public async Task TryInitialize_returns_an_ErrorTimeout()
{
// Arrange
var nativeTag = new Mock<INativeTag>();

nativeTag // The initial creation of the tag object returns a status, so we return pending
.Setup(m => m.plc_tag_create(It.IsAny<string>(), 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<int>()))
.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<INativeTag>();

nativeTag // The initial creation of the tag object returns a status, so we return pending
.Setup(m => m.plc_tag_create(It.IsAny<string>(), 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<int>()))
.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<TaskCanceledException>(async () => {
await tag.TryInitializeAsync(cts.Token);
});
}

[Fact]
public async Task Timeout_returns_pending_but_eventually_ok()
{
Expand Down
8 changes: 8 additions & 0 deletions src/libplctag/ITag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,18 @@ public interface ITag : IDisposable
Status GetStatus();
void Initialize();
Task InitializeAsync(CancellationToken token = default);
bool TryInitialize();
Task<bool> TryInitializeAsync(CancellationToken token = default);

object Read();
Task<object> ReadAsync(CancellationToken token = default);
bool TryRead();
Task<bool> TryReadAsync(CancellationToken token = default);

void Write();
Task WriteAsync(CancellationToken token = default);
bool TryWrite();
Task<bool> TryWriteAsync(CancellationToken token = default);

object Value { get; set; }
}
Expand Down
91 changes: 65 additions & 26 deletions src/libplctag/NativeTagWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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<Status> TryInitializeAsync(CancellationToken token = default)
{

ThrowIfAlreadyDisposed();
ThrowIfAlreadyInitialized();

Expand All @@ -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);
}
}))
{
Expand All @@ -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<Status> TryReadAsync(CancellationToken token = default)
{
ThrowIfAlreadyDisposed();
await InitializeAsyncIfRequired(token).ConfigureAwait(false);
Expand All @@ -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<Status>(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<Status> TryWriteAsync(CancellationToken token = default)
{
ThrowIfAlreadyDisposed();
await InitializeAsyncIfRequired(token).ConfigureAwait(false);
Expand All @@ -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<Status>(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;
}
}
}
Expand Down Expand Up @@ -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<T>(Func<int, int, T, int> nativeMethod, int offset, T value)
{
ThrowIfAlreadyDisposed();
Expand Down Expand Up @@ -735,9 +777,6 @@ string FormatPlcType(PlcType? type)

}




void SetUpEvents()
{

Expand Down
52 changes: 52 additions & 0 deletions src/libplctag/Tag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ public uint? StringTotalLength
/// Can only be called once per instance.
/// Timeout is controlled via class property.
/// </remarks>
/// <exception cref="LibPlcTagException"></exception>
public void Initialize() => _tag.Initialize();

/// <summary>
Expand Down Expand Up @@ -384,6 +385,55 @@ public uint? StringTotalLength
/// </remarks>
public Task WriteAsync(CancellationToken token = default) => _tag.WriteAsync(token);

/// <summary>
/// Creates the underlying data structures and references required before tag operations.
/// </summary>
///
/// <returns>
/// Whether the operation was successful.
/// </returns>
///
/// <remarks>
/// Initializes the tag by establishing necessary connections.
/// Can only be called once per instance.
/// Timeout is controlled via class property.
/// </remarks>
public bool TryInitialize()
{
var status = _tag.TryInitialize();
return _tag.IsStatusOk(status);
}

public async Task<bool> 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<bool> 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<bool> TryWriteAsync(CancellationToken token = default)
{
var status = await _tag.TryWriteAsync(token);
return _tag.IsStatusOk(status);
}

public void Abort() => _tag.Abort();
public void Dispose() => _tag.Dispose();

Expand Down Expand Up @@ -418,6 +468,8 @@ public uint? StringTotalLength
/// <returns>Tag's current status</returns>
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);

Expand Down
Loading
Loading