From 68b0140ea5cb2a690cc50289c2b4e2e4fc3e098d Mon Sep 17 00:00:00 2001 From: sakno Date: Tue, 15 Oct 2024 21:14:21 +0300 Subject: [PATCH] Added synchronous Wait for async events --- .../Threading/AsyncExclusiveLockTests.cs | 2 +- .../Threading/AsyncReaderWriterLockTests.cs | 6 ++--- .../Threading/AsyncResetEventTests.cs | 23 +++++++++++++++++++ .../Threading/AsyncAutoResetEvent.cs | 17 ++++++++++++++ .../Threading/AsyncManualResetEvent.cs | 16 +++++++++++++ .../Threading/IAsyncEvent.cs | 13 +++++++++++ .../Threading/QueuedSynchronizer.cs | 12 ++++++++++ 7 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/DotNext.Tests/Threading/AsyncExclusiveLockTests.cs b/src/DotNext.Tests/Threading/AsyncExclusiveLockTests.cs index 7c3eb7b70..2f7bb885e 100644 --- a/src/DotNext.Tests/Threading/AsyncExclusiveLockTests.cs +++ b/src/DotNext.Tests/Threading/AsyncExclusiveLockTests.cs @@ -195,7 +195,7 @@ public static async Task MixedLock() await using var l = new AsyncExclusiveLock(); True(await l.TryAcquireAsync(DefaultTimeout)); - var t = new Thread(() => l.TryAcquire(DefaultTimeout)) { IsBackground = true }; + var t = new Thread(() => True(l.TryAcquire(DefaultTimeout))) { IsBackground = true }; t.Start(); l.Release(); diff --git a/src/DotNext.Tests/Threading/AsyncReaderWriterLockTests.cs b/src/DotNext.Tests/Threading/AsyncReaderWriterLockTests.cs index a6e904f89..dc512c6a2 100644 --- a/src/DotNext.Tests/Threading/AsyncReaderWriterLockTests.cs +++ b/src/DotNext.Tests/Threading/AsyncReaderWriterLockTests.cs @@ -231,7 +231,7 @@ public static void AcquireReadWriteLockSynchronously() True(l.TryEnterReadLock(DefaultTimeout)); Equal(2L, l.CurrentReadCount); - var t = new Thread(() => l.TryEnterWriteLock(DefaultTimeout)) { IsBackground = true }; + var t = new Thread(() => True(l.TryEnterWriteLock(DefaultTimeout))) { IsBackground = true }; t.Start(); l.Release(); @@ -250,8 +250,8 @@ public static void ResumeMultipleReadersSynchronously() var l = new AsyncReaderWriterLock(); True(l.TryEnterWriteLock()); - var t1 = new Thread(() => l.TryEnterReadLock(DefaultTimeout)) { IsBackground = true }; - var t2 = new Thread(() => l.TryEnterReadLock(DefaultTimeout)) { IsBackground = true }; + var t1 = new Thread(() => True(l.TryEnterReadLock(DefaultTimeout))) { IsBackground = true }; + var t2 = new Thread(() => True(l.TryEnterReadLock(DefaultTimeout))) { IsBackground = true }; t1.Start(); t2.Start(); diff --git a/src/DotNext.Tests/Threading/AsyncResetEventTests.cs b/src/DotNext.Tests/Threading/AsyncResetEventTests.cs index 5f7f00ec3..b21866313 100644 --- a/src/DotNext.Tests/Threading/AsyncResetEventTests.cs +++ b/src/DotNext.Tests/Threading/AsyncResetEventTests.cs @@ -104,4 +104,27 @@ public static async Task RegressionIssue82() ev.Set(); await consumer; } + + public static TheoryData GetResetEvents() => new() + { + new AsyncAutoResetEvent(false), + new AsyncManualResetEvent(false), + }; + + [Theory] + [MemberData(nameof(GetResetEvents))] + public static void ManualResetEventSynchronousCompletion(IAsyncResetEvent resetEvent) + { + using (resetEvent) + { + False(resetEvent.IsSet); + + var t = new Thread(() => True(resetEvent.Wait(DefaultTimeout))) { IsBackground = true }; + t.Start(); + + True(resetEvent.Signal()); + True(t.Join(DefaultTimeout)); + Equal(resetEvent.ResetMode is EventResetMode.ManualReset, resetEvent.IsSet); + } + } } \ No newline at end of file diff --git a/src/DotNext.Threading/Threading/AsyncAutoResetEvent.cs b/src/DotNext.Threading/Threading/AsyncAutoResetEvent.cs index 08d5e89c2..3384a8b8b 100644 --- a/src/DotNext.Threading/Threading/AsyncAutoResetEvent.cs +++ b/src/DotNext.Threading/Threading/AsyncAutoResetEvent.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Runtime.InteropServices; +using System.Runtime.Versioning; namespace DotNext.Threading; @@ -117,6 +118,8 @@ public bool Set() break; } } + + Monitor.PulseAll(SyncRoot); } } @@ -151,4 +154,18 @@ public ValueTask WaitAsync(TimeSpan timeout, CancellationToken token = def /// The operation has been canceled. public ValueTask WaitAsync(CancellationToken token = default) => AcquireAsync(ref pool, ref manager, new CancellationTokenOnly(token)); + + /// + /// Blocks the current thread until this event is set. + /// + /// The time to wait for the event. + /// , if this event was set; otherwise, . + /// The current instance has already been disposed. + /// is negative. + [UnsupportedOSPlatform("browser")] + public bool Wait(TimeSpan timeout) + { + ObjectDisposedException.ThrowIf(IsDisposingOrDisposed, this); + return Wait(new(timeout), ref manager); + } } \ No newline at end of file diff --git a/src/DotNext.Threading/Threading/AsyncManualResetEvent.cs b/src/DotNext.Threading/Threading/AsyncManualResetEvent.cs index f95e4799c..0f54c9b37 100644 --- a/src/DotNext.Threading/Threading/AsyncManualResetEvent.cs +++ b/src/DotNext.Threading/Threading/AsyncManualResetEvent.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Runtime.Versioning; namespace DotNext.Threading; @@ -107,6 +108,7 @@ public bool Set(bool autoReset) result = !manager.Value; manager.Value = !autoReset; suspendedCallers = DetachWaitQueue()?.SetResult(true, out _); + Monitor.PulseAll(SyncRoot); } suspendedCallers?.Unwind(); @@ -153,4 +155,18 @@ public ValueTask WaitAsync(TimeSpan timeout, CancellationToken token = def /// The operation has been canceled. public ValueTask WaitAsync(CancellationToken token = default) => AcquireAsync(ref pool, ref manager, new CancellationTokenOnly(token)); + + /// + /// Blocks the current thread until this event is set. + /// + /// The time to wait for the event. + /// , if this event was set; otherwise, . + /// The current instance has already been disposed. + /// is negative. + [UnsupportedOSPlatform("browser")] + public bool Wait(TimeSpan timeout) + { + ObjectDisposedException.ThrowIf(IsDisposingOrDisposed, this); + return Wait(new(timeout), ref manager); + } } \ No newline at end of file diff --git a/src/DotNext.Threading/Threading/IAsyncEvent.cs b/src/DotNext.Threading/Threading/IAsyncEvent.cs index 93fff9758..5b5bab492 100644 --- a/src/DotNext.Threading/Threading/IAsyncEvent.cs +++ b/src/DotNext.Threading/Threading/IAsyncEvent.cs @@ -46,4 +46,17 @@ public interface IAsyncEvent : IDisposable, IResettable /// The current instance has already been disposed. /// The operation has been canceled. ValueTask WaitAsync(CancellationToken token = default); + + /// + /// Blocks the current thread until this event is set. + /// + /// The time to wait for the event. + /// , if this event was set; otherwise, . + /// The current instance has already been disposed. + /// is negative. + bool Wait(TimeSpan timeout) + { + using var task = WaitAsync(timeout).AsTask(); + return task.Wait(timeout); + } } \ No newline at end of file diff --git a/src/DotNext.Threading/Threading/QueuedSynchronizer.cs b/src/DotNext.Threading/Threading/QueuedSynchronizer.cs index 8d2c43e82..ab94f59a8 100644 --- a/src/DotNext.Threading/Threading/QueuedSynchronizer.cs +++ b/src/DotNext.Threading/Threading/QueuedSynchronizer.cs @@ -188,6 +188,18 @@ private protected bool TryAcquire(Timeout timeout, ref TLockManage return true; } + [UnsupportedOSPlatform("browser")] + private protected bool Wait(Timeout timeout, ref TLockManager manager) + where TLockManager : struct, ILockManager + { + lock (SyncRoot) + { + return TryAcquire(ref manager) || + timeout.TryGetRemainingTime(out var remainingTime) + && Monitor.Wait(SyncRoot, remainingTime); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool TryAcquireOrThrow(ref TLockManager manager) where TLockManager : struct, ILockManager