diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs index f0e0abff52..1d97376444 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs @@ -249,7 +249,6 @@ public async Task EndAsync( try { Task awaitableTask = m_tcs.Task; -#if NET6_0_OR_GREATER if (timeout != int.MaxValue) { awaitableTask = m_tcs.Task @@ -259,34 +258,9 @@ public async Task EndAsync( { awaitableTask = m_tcs.Task.WaitAsync(ct); } -#else - if (timeout != int.MaxValue || ct != default) + if (!await awaitableTask.ConfigureAwait(false)) { - using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct); - Task delay = Task.Delay(timeout, cts.Token); - Task completedTask = await Task.WhenAny(m_tcs.Task, delay) - .ConfigureAwait(false); - cts.Cancel(); // Always cancel the (possibly infinite) timeout - if (m_tcs.Task == completedTask) - { - if (!m_tcs.Task.Result) - { - badRequestInterrupted = true; - } - } - else - { - m_tcs.TrySetCanceled(ct); - badRequestInterrupted = true; - } - } - else -#endif - { - if (!await awaitableTask.ConfigureAwait(false)) - { - badRequestInterrupted = true; - } + badRequestInterrupted = true; } } catch (TimeoutException) diff --git a/Stack/Opc.Ua.Types/Polyfills/System.Threading.Tasks.cs b/Stack/Opc.Ua.Types/Polyfills/System.Threading.Tasks.cs index fba2c7c5d4..7444fbc9df 100644 --- a/Stack/Opc.Ua.Types/Polyfills/System.Threading.Tasks.cs +++ b/Stack/Opc.Ua.Types/Polyfills/System.Threading.Tasks.cs @@ -34,6 +34,51 @@ namespace System.Threading.Tasks /// public static class PolyFills { +#if !NET6_0_OR_GREATER + /// + /// Gets a System.Threading.Tasks.Task that will complete when this System.Threading.Tasks.Task + /// completes, when the specified timeout expires, or when the specified System.Threading.CancellationToken + /// + /// Task return type + /// The task to wait for. Can't be + /// + /// The timeout after which the System.Threading.Tasks.Task should be faulted with + ///

a System.TimeoutException if it hasn't otherwise completed. + /// + /// The System.Threading.CancellationToken to monitor for a cancellation request. + /// The System.Threading.Tasks.Task representing the asynchronous wait. It may or + /// may not be the same instance as the current instance. + /// + /// + public static async Task WaitAsync(this Task task, TimeSpan timeout, CancellationToken ct = default) + { + if (task is null) + { + throw new ArgumentNullException(nameof(task)); + } + using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + cts.CancelAfter(timeout); + try + { + var tcs = new TaskCompletionSource(); + using (cts.Token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: false)) + { + Task completedTask = await Task.WhenAny(task, tcs.Task).ConfigureAwait(false); + if (task != completedTask) + { + ct.ThrowIfCancellationRequested(); + throw new TimeoutException("The operation has timed out."); + } + } + return await task.ConfigureAwait(false); + } + catch (OperationCanceledException) when (!ct.IsCancellationRequested) + { + throw new TimeoutException("The operation has timed out."); + } + } +#endif + #if !NET8_0_OR_GREATER // Copyright Stephen Cleary Nito.AsyncEx @@ -72,7 +117,7 @@ private static async Task DoWaitAsync( Task task, CancellationToken cancellationToken) { - var cancelTaskSource = + using var cancelTaskSource = new CancellationTokenTaskSource(cancellationToken); await (await Task.WhenAny(task, cancelTaskSource.Task).ConfigureAwait(false)) .ConfigureAwait(false); diff --git a/version.json b/version.json index 2384e1c39b..2dc9e72bb2 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "1.5.378-preview", + "version": "1.5.378", "versionHeightOffset": 0, "nugetPackageVersion": { "semVer": 2