Skip to content

Commit

Permalink
Added synchronous Wait for async events
Browse files Browse the repository at this point in the history
  • Loading branch information
sakno committed Oct 15, 2024
1 parent a8e9ede commit 68b0140
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/DotNext.Tests/Threading/AsyncExclusiveLockTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
6 changes: 3 additions & 3 deletions src/DotNext.Tests/Threading/AsyncReaderWriterLockTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();

Expand Down
23 changes: 23 additions & 0 deletions src/DotNext.Tests/Threading/AsyncResetEventTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,27 @@ public static async Task RegressionIssue82()
ev.Set();
await consumer;
}

public static TheoryData<IAsyncResetEvent> 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);
}
}
}
17 changes: 17 additions & 0 deletions src/DotNext.Threading/Threading/AsyncAutoResetEvent.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

namespace DotNext.Threading;

Expand Down Expand Up @@ -117,6 +118,8 @@ public bool Set()
break;
}
}

Monitor.PulseAll(SyncRoot);
}
}

Expand Down Expand Up @@ -151,4 +154,18 @@ public ValueTask<bool> WaitAsync(TimeSpan timeout, CancellationToken token = def
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
public ValueTask WaitAsync(CancellationToken token = default)
=> AcquireAsync(ref pool, ref manager, new CancellationTokenOnly(token));

/// <summary>
/// Blocks the current thread until this event is set.
/// </summary>
/// <param name="timeout">The time to wait for the event.</param>
/// <returns><see langword="true"/>, if this event was set; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ObjectDisposedException">The current instance has already been disposed.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> is negative.</exception>
[UnsupportedOSPlatform("browser")]
public bool Wait(TimeSpan timeout)
{
ObjectDisposedException.ThrowIf(IsDisposingOrDisposed, this);
return Wait(new(timeout), ref manager);
}
}
16 changes: 16 additions & 0 deletions src/DotNext.Threading/Threading/AsyncManualResetEvent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Runtime.Versioning;

namespace DotNext.Threading;

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -153,4 +155,18 @@ public ValueTask<bool> WaitAsync(TimeSpan timeout, CancellationToken token = def
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
public ValueTask WaitAsync(CancellationToken token = default)
=> AcquireAsync(ref pool, ref manager, new CancellationTokenOnly(token));

/// <summary>
/// Blocks the current thread until this event is set.
/// </summary>
/// <param name="timeout">The time to wait for the event.</param>
/// <returns><see langword="true"/>, if this event was set; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ObjectDisposedException">The current instance has already been disposed.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> is negative.</exception>
[UnsupportedOSPlatform("browser")]
public bool Wait(TimeSpan timeout)
{
ObjectDisposedException.ThrowIf(IsDisposingOrDisposed, this);
return Wait(new(timeout), ref manager);
}
}
13 changes: 13 additions & 0 deletions src/DotNext.Threading/Threading/IAsyncEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,17 @@ public interface IAsyncEvent : IDisposable, IResettable
/// <exception cref="ObjectDisposedException">The current instance has already been disposed.</exception>
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
ValueTask WaitAsync(CancellationToken token = default);

/// <summary>
/// Blocks the current thread until this event is set.
/// </summary>
/// <param name="timeout">The time to wait for the event.</param>
/// <returns><see langword="true"/>, if this event was set; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ObjectDisposedException">The current instance has already been disposed.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="timeout"/> is negative.</exception>
bool Wait(TimeSpan timeout)
{
using var task = WaitAsync(timeout).AsTask();
return task.Wait(timeout);
}
}
12 changes: 12 additions & 0 deletions src/DotNext.Threading/Threading/QueuedSynchronizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,18 @@ private protected bool TryAcquire<TLockManager>(Timeout timeout, ref TLockManage
return true;
}

[UnsupportedOSPlatform("browser")]
private protected bool Wait<TLockManager>(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<TLockManager>(ref TLockManager manager)
where TLockManager : struct, ILockManager
Expand Down

0 comments on commit 68b0140

Please sign in to comment.