From b2ea5489417917e55e33fbb90bc283b7c85258f8 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 30 Sep 2024 08:27:23 -0400 Subject: [PATCH] feat: Reduce allocations when using AsyncLock (+ remove all dependencies) --- .../Threading/AsyncLock.cs | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/Uno.Foundation/Uno.Core.Extensions/Uno.Core.Extensions.Compatibility/Threading/AsyncLock.cs b/src/Uno.Foundation/Uno.Core.Extensions/Uno.Core.Extensions.Compatibility/Threading/AsyncLock.cs index 61a61722124e..343e1a8acb25 100644 --- a/src/Uno.Foundation/Uno.Core.Extensions/Uno.Core.Extensions.Compatibility/Threading/AsyncLock.cs +++ b/src/Uno.Foundation/Uno.Core.Extensions/Uno.Core.Extensions.Compatibility/Threading/AsyncLock.cs @@ -1,3 +1,4 @@ +#nullable enable // ****************************************************************** // Copyright � 2015-2018 Uno Platform Inc. All rights reserved. // @@ -18,27 +19,37 @@ using System.Threading; using System.Threading.Tasks; -using Uno.Disposables; +namespace Uno.Threading; -namespace Uno.Threading +/// +/// An asynchronous lock, that can be used in conjuction with C# async/await +/// +internal sealed class AsyncLock { + private readonly SemaphoreSlim _semaphore = new(1, 1); + private ulong _handleId; + /// - /// An asynchronous lock, that can be used in conjuction with C# async/await + /// Acquires the lock, then provides a disposable to release it. + /// WARNING: This DOES NOT support reentrancy. /// - internal sealed class AsyncLock + /// A cancellation token to cancel the lock + /// An IDisposable instance that allows the release of the lock. + public async ValueTask LockAsync(CancellationToken ct) { - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); + await _semaphore.WaitAsync(ct); - /// - /// Acquires the lock, then provides a disposable to release it. - /// - /// A cancellation token to cancel the lock - /// An IDisposable instance that allows the release of the lock. - public async Task LockAsync(CancellationToken ct) - { - await _semaphore.WaitAsync(ct); + return new Handle(this, _handleId); + } - return Disposable.Create(() => _semaphore.Release()); + public record struct Handle(AsyncLock Lock, ulong Id) : IDisposable + { + public void Dispose() + { + if (Interlocked.CompareExchange(ref Lock._handleId, Id + 1, Id) == Id) // This avoids (concurrent) double dispose / release + { + Lock._semaphore.Release(); + } } } }