diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs
index feba487b4d..687cbbf3b2 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs
@@ -31,6 +31,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using System.Threading.Tasks.Sources;
using Microsoft.Extensions.Logging;
namespace Opc.Ua.Bindings
@@ -39,7 +40,7 @@ namespace Opc.Ua.Bindings
/// Stores the results of an asynchronous operation.
///
///
- public class ChannelAsyncOperation : IAsyncResult, IDisposable
+ public class ChannelAsyncOperation : IAsyncResult, IDisposable, IValueTaskSource, IValueTaskSource
{
///
/// Initializes the object with a callback
@@ -51,10 +52,13 @@ public ChannelAsyncOperation(int timeout, AsyncCallback? callback, object? async
m_synchronous = false;
m_completed = false;
m_logger = logger;
+ m_asyncWaitSource.RunContinuationsAsynchronously = true;
if (timeout is > 0 and not int.MaxValue)
{
- m_timer = new Timer(new TimerCallback(OnTimeout), null, timeout, Timeout.Infinite);
+ m_timeoutCancellationTokenSource = new CancellationTokenSource(timeout);
+ m_timeoutCancellationRegistration =
+ m_timeoutCancellationTokenSource.Token.Register(OnTimeout);
}
}
@@ -76,8 +80,9 @@ protected virtual void Dispose(bool disposing)
{
lock (m_lock)
{
- m_timer?.Dispose();
- m_timer = null;
+ m_timeoutCancellationRegistration.Dispose();
+ m_timeoutCancellationTokenSource?.Dispose();
+ m_timeoutCancellationTokenSource = null;
if (m_event != null)
{
@@ -86,13 +91,11 @@ protected virtual void Dispose(bool disposing)
m_event = null;
}
- if (m_tcs != null)
+ if (m_asyncWaitPending)
{
- if (!m_tcs.Task.IsCompleted)
- {
- m_tcs.TrySetCanceled();
- }
- m_tcs = null;
+ m_asyncWaitSource.SetException(
+ new TaskCanceledException("ChannelAsyncOperation was disposed while an async wait was pending."));
+ m_asyncWaitPending = false;
}
}
}
@@ -223,13 +226,25 @@ public T End(int timeout, bool throwOnError = true)
/// The awaitable response returned from the server.
///
///
- public async Task EndAsync(
+ public Task EndAsync(
+ int timeout,
+ bool throwOnError = true,
+ CancellationToken ct = default)
+ {
+ return EndValueTaskAsync(timeout, throwOnError, ct).AsTask();
+ }
+
+ ///
+ /// The low-allocation awaitable response returned from the server.
+ ///
+ internal async ValueTask EndValueTaskAsync(
int timeout,
bool throwOnError = true,
CancellationToken ct = default)
{
// check if the request has already completed.
bool mustWait = false;
+ ValueTask waitTask = default;
lock (m_lock)
{
@@ -237,8 +252,8 @@ public async Task EndAsync(
if (mustWait)
{
- m_tcs = new TaskCompletionSource(
- TaskCreationOptions.RunContinuationsAsynchronously);
+ m_asyncWaitPending = true;
+ waitTask = new ValueTask(this, m_asyncWaitSource.Version);
}
}
@@ -248,19 +263,13 @@ public async Task EndAsync(
bool badRequestInterrupted = false;
try
{
- Task awaitableTask = m_tcs!.Task;
- if (timeout != int.MaxValue)
+ if (timeout != int.MaxValue || ct.CanBeCanceled)
{
- awaitableTask = m_tcs.Task
- .WaitAsync(TimeSpan.FromMilliseconds(timeout), ct);
+ await WaitAsync(waitTask, timeout, ct).ConfigureAwait(false);
}
- else if (ct != default)
+ else
{
- awaitableTask = m_tcs.Task.WaitAsync(ct);
- }
- if (!await awaitableTask.ConfigureAwait(false))
- {
- badRequestInterrupted = true;
+ _ = await waitTask.ConfigureAwait(false);
}
}
catch (TimeoutException)
@@ -271,12 +280,9 @@ public async Task EndAsync(
{
badRequestInterrupted = true;
}
- finally
+ catch (OperationCanceledException)
{
- lock (m_lock)
- {
- m_tcs = null;
- }
+ badRequestInterrupted = true;
}
if (badRequestInterrupted && throwOnError)
@@ -368,17 +374,6 @@ public bool IsCompleted
}
}
- ///
- /// Called when the operation times out.
- ///
- private void OnTimeout(object? state)
- {
- if (m_timer != null)
- {
- InternalComplete(false, new ServiceResult(StatusCodes.BadRequestTimeout));
- }
- }
-
///
/// Called when an asynchronous operation completes.
///
@@ -403,12 +398,17 @@ protected virtual bool InternalComplete(bool doNotBlock, object? result)
m_completed = true;
- m_timer?.Dispose();
- m_timer = null;
+ m_timeoutCancellationRegistration.Dispose();
+ m_timeoutCancellationTokenSource?.Dispose();
+ m_timeoutCancellationTokenSource = null;
m_event?.Set();
- m_tcs?.TrySetResult(true);
+ if (m_asyncWaitPending)
+ {
+ m_asyncWaitSource.SetResult(true);
+ m_asyncWaitPending = false;
+ }
}
AsyncCallback? callback = m_callback;
@@ -436,17 +436,102 @@ protected virtual bool InternalComplete(bool doNotBlock, object? result)
return true;
}
+ ValueTaskSourceStatus IValueTaskSource.GetStatus(short token)
+ {
+ return m_asyncWaitSource.GetStatus(token);
+ }
+
+ ValueTaskSourceStatus IValueTaskSource.GetStatus(short token)
+ {
+ return m_asyncWaitSource.GetStatus(token);
+ }
+
+ bool IValueTaskSource.GetResult(short token)
+ {
+ return m_asyncWaitSource.GetResult(token);
+ }
+
+ void IValueTaskSource.GetResult(short token)
+ {
+ ((IValueTaskSource)m_asyncWaitSource).GetResult(token);
+ }
+
+ void IValueTaskSource.OnCompleted(
+ Action