Skip to content

Commit

Permalink
Performance improvement on AsyncNonKeyedLocker.
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkCiliaVincenti committed Jan 14, 2024
1 parent 889a0c0 commit 55472ca
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 26 deletions.
20 changes: 8 additions & 12 deletions AsyncKeyedLock.Tests/AsyncNonKeyedLockerTests/OriginalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ public void TestMaxCount()
{
Assert.Equal(1, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
Assert.Equal(1, myLock.SemaphoreSlim.CurrentCount);
using (var myLock2 = (AsyncNonKeyedLockReleaser)asyncNonKeyedLocker.Lock())
{
Assert.Equal(asyncNonKeyedLocker.MaxCount, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(0, asyncNonKeyedLocker.GetCurrentCount());
Assert.Equal(0, myLock2.SemaphoreSlim.CurrentCount);
}
}
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Expand All @@ -37,7 +35,6 @@ public void TestLock()
{
Assert.Equal(1, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(0, asyncNonKeyedLocker.GetCurrentCount());
Assert.Equal(0, myLock.SemaphoreSlim.CurrentCount);
}
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(1, asyncNonKeyedLocker.GetCurrentCount());
Expand Down Expand Up @@ -79,14 +76,13 @@ public void TestLockAndMillisecondsTimeout()
using (var myLock = asyncNonKeyedLocker.Lock(0, out bool entered))
{
Assert.True(entered);
Assert.True(myLock.EnteredSemaphore);
Assert.True(((AsyncNonKeyedLockTimeoutReleaser)myLock).EnteredSemaphore);
Assert.Equal(1, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(0, asyncNonKeyedLocker.GetCurrentCount());
Assert.Equal(0, myLock.SemaphoreSlim.CurrentCount);
using (var myLock2 = asyncNonKeyedLocker.Lock(0, out entered))
{
Assert.False(entered);
Assert.False(myLock2.EnteredSemaphore);
Assert.False(((AsyncNonKeyedLockTimeoutReleaser)myLock2).EnteredSemaphore);
}
}
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Expand All @@ -102,13 +98,13 @@ public void TestLockAndTimeout()
using (var myLock = asyncNonKeyedLocker.Lock(TimeSpan.FromMilliseconds(0), out bool entered))
{
Assert.True(entered);
Assert.True(myLock.EnteredSemaphore);
Assert.True(((AsyncNonKeyedLockTimeoutReleaser)myLock).EnteredSemaphore);
Assert.Equal(1, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(0, asyncNonKeyedLocker.GetCurrentCount());
using (var myLock2 = asyncNonKeyedLocker.Lock(TimeSpan.FromMilliseconds(0), out entered))
{
Assert.False(entered);
Assert.False(myLock2.EnteredSemaphore);
Assert.False(((AsyncNonKeyedLockTimeoutReleaser)myLock2).EnteredSemaphore);
}
}
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Expand All @@ -124,13 +120,13 @@ public void TestLockAndMillisecondsTimeoutAndCancellationToken()
using (var myLock = asyncNonKeyedLocker.Lock(0, CancellationToken.None, out bool entered))
{
Assert.True(entered);
Assert.True(myLock.EnteredSemaphore);
Assert.True(((AsyncNonKeyedLockTimeoutReleaser)myLock).EnteredSemaphore);
Assert.Equal(1, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(0, asyncNonKeyedLocker.GetCurrentCount());
using (var myLock2 = asyncNonKeyedLocker.Lock(0, CancellationToken.None, out entered))
{
Assert.False(entered);
Assert.False(myLock2.EnteredSemaphore);
Assert.False(((AsyncNonKeyedLockTimeoutReleaser)myLock2).EnteredSemaphore);
}
}
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Expand Down Expand Up @@ -160,13 +156,13 @@ public void TestLockAndTimeoutAndCancellationToken()
using (var myLock = asyncNonKeyedLocker.Lock(TimeSpan.FromMilliseconds(0), CancellationToken.None, out bool entered))
{
Assert.True(entered);
Assert.True(myLock.EnteredSemaphore);
Assert.True(((AsyncNonKeyedLockTimeoutReleaser)myLock).EnteredSemaphore);
Assert.Equal(1, asyncNonKeyedLocker.GetRemainingCount());
Assert.Equal(0, asyncNonKeyedLocker.GetCurrentCount());
using (var myLock2 = asyncNonKeyedLocker.Lock(TimeSpan.FromMilliseconds(0), CancellationToken.None, out entered))
{
Assert.False(entered);
Assert.False(myLock2.EnteredSemaphore);
Assert.False(((AsyncNonKeyedLockTimeoutReleaser)myLock2).EnteredSemaphore);
}
}
Assert.Equal(0, asyncNonKeyedLocker.GetRemainingCount());
Expand Down
6 changes: 3 additions & 3 deletions AsyncKeyedLock/AsyncKeyedLock.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
<PackageProjectUrl>https://github.com/MarkCiliaVincenti/AsyncKeyedLock</PackageProjectUrl>
<Copyright>MIT</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Version>6.3.1</Version>
<Version>6.3.2</Version>
<PackageIcon>logo.png</PackageIcon>
<PackageReleaseNotes>Performance improvement on AsyncNonKeyedLocker.</PackageReleaseNotes>
<Description>An asynchronous .NET Standard 2.0 library that allows you to lock based on a key (keyed semaphores), limiting concurrent threads sharing the same key to a specified number, with optional pooling for reducing memory allocations.</Description>
<Copyright>© 2024 Mark Cilia Vincenti</Copyright>
<PackageTags>async,lock,key,keyed,semaphore,striped,dictionary,concurrentdictionary,pooling,duplicate,synchronization</PackageTags>
<RepositoryType>git</RepositoryType>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<AssemblyVersion>6.3.1.0</AssemblyVersion>
<FileVersion>6.3.1.0</FileVersion>
<AssemblyVersion>6.3.2.0</AssemblyVersion>
<FileVersion>6.3.2.0</FileVersion>
<PackageReadmeFile>README.md</PackageReadmeFile>
<IsPackable>true</IsPackable>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
Expand Down
5 changes: 5 additions & 0 deletions AsyncKeyedLock/AsyncNonKeyedLockTimeoutReleaser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ namespace AsyncKeyedLock
{
private readonly bool _enteredSemaphore;

/// <summary>
/// True if the timeout was reached, false if not.
/// </summary>
public readonly bool EnteredSemaphore => _enteredSemaphore;

private readonly AsyncNonKeyedLocker _locker;

internal AsyncNonKeyedLockTimeoutReleaser(AsyncNonKeyedLocker locker, bool enteredSemaphore)
Expand Down
46 changes: 35 additions & 11 deletions AsyncKeyedLock/AsyncNonKeyedLocker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,16 @@ public IDisposable Lock(CancellationToken cancellationToken)
/// </summary>
/// <param name="millisecondsTimeout">The number of milliseconds to wait, <see cref="Timeout.Infinite"/> (-1) to wait indefinitely, or zero to test the state of the wait handle and return immediately.</param>
/// <param name="entered">An out parameter showing whether or not the semaphore was entered.</param>
/// <returns>A disposable value of type <see cref="AsyncNonKeyedLockTimeoutReleaser"/>.</returns>
/// <returns>A disposable value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public AsyncNonKeyedLockTimeoutReleaser Lock(int millisecondsTimeout, out bool entered)
public IDisposable Lock(int millisecondsTimeout, out bool entered)
{
if (millisecondsTimeout == Timeout.Infinite)
{
entered = true;
_semaphoreSlim.Wait();
return new AsyncNonKeyedLockReleaser(this);
}
entered = _semaphoreSlim.Wait(millisecondsTimeout);
return new AsyncNonKeyedLockTimeoutReleaser(this, entered);
}
Expand All @@ -70,10 +76,16 @@ public AsyncNonKeyedLockTimeoutReleaser Lock(int millisecondsTimeout, out bool e
/// </summary>
/// <param name="timeout">A <see cref="TimeSpan"/> that represents the number of milliseconds to wait, a <see cref="TimeSpan"/> that represents -1 milliseconds to wait indefinitely, or a <see cref="TimeSpan"/> that represents 0 milliseconds to test the wait handle and return immediately.</param>
/// <param name="entered">An out parameter showing whether or not the semaphore was entered.</param>
/// <returns>A disposable value of type <see cref="AsyncNonKeyedLockTimeoutReleaser"/>.</returns>
/// <returns>A disposable value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public AsyncNonKeyedLockTimeoutReleaser Lock(TimeSpan timeout, out bool entered)
public IDisposable Lock(TimeSpan timeout, out bool entered)
{
if (timeout.TotalMilliseconds == Timeout.Infinite)
{
entered = true;
_semaphoreSlim.Wait();
return new AsyncNonKeyedLockReleaser(this);
}
entered = _semaphoreSlim.Wait(timeout);
return new AsyncNonKeyedLockTimeoutReleaser(this, entered);
}
Expand All @@ -84,20 +96,26 @@ public AsyncNonKeyedLockTimeoutReleaser Lock(TimeSpan timeout, out bool entered)
/// <param name="millisecondsTimeout">The number of milliseconds to wait, <see cref="Timeout.Infinite"/> (-1) to wait indefinitely, or zero to test the state of the wait handle and return immediately.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <param name="entered">An out parameter showing whether or not the semaphore was entered.</param>
/// <returns>A disposable value of type <see cref="AsyncNonKeyedLockTimeoutReleaser"/>.</returns>
/// <returns>A disposable value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public AsyncNonKeyedLockTimeoutReleaser Lock(int millisecondsTimeout, CancellationToken cancellationToken, out bool entered)
public IDisposable Lock(int millisecondsTimeout, CancellationToken cancellationToken, out bool entered)
{
try
{
if (millisecondsTimeout == Timeout.Infinite)
{
entered = true;
_semaphoreSlim.Wait(cancellationToken);
return new AsyncNonKeyedLockReleaser(this);
}
entered = _semaphoreSlim.Wait(millisecondsTimeout, cancellationToken);
return new AsyncNonKeyedLockTimeoutReleaser(this, entered);
}
catch (OperationCanceledException)
{
entered = false;
throw;
}
return new AsyncNonKeyedLockTimeoutReleaser(this, entered);
}
}

/// <summary>
Expand All @@ -106,20 +124,26 @@ public AsyncNonKeyedLockTimeoutReleaser Lock(int millisecondsTimeout, Cancellati
/// <param name="timeout">A <see cref="TimeSpan"/> that represents the number of milliseconds to wait, a <see cref="TimeSpan"/> that represents -1 milliseconds to wait indefinitely, or a <see cref="TimeSpan"/> that represents 0 milliseconds to test the wait handle and return immediately.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <param name="entered">An out parameter showing whether or not the semaphore was entered.</param>
/// <returns>A disposable value of type <see cref="AsyncNonKeyedLockTimeoutReleaser"/>.</returns>
/// <returns>A disposable value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public AsyncNonKeyedLockTimeoutReleaser Lock(TimeSpan timeout, CancellationToken cancellationToken, out bool entered)
public IDisposable Lock(TimeSpan timeout, CancellationToken cancellationToken, out bool entered)
{
try
{
if (timeout.TotalMilliseconds == Timeout.Infinite)
{
entered = true;
_semaphoreSlim.Wait(cancellationToken);
return new AsyncNonKeyedLockReleaser(this);
}
entered = _semaphoreSlim.Wait(timeout, cancellationToken);
return new AsyncNonKeyedLockTimeoutReleaser(this, entered);
}
catch (OperationCanceledException)
{
entered = false;
throw;
}
return new AsyncNonKeyedLockTimeoutReleaser(this, entered);
}
#endregion Synchronous

Expand Down

0 comments on commit 55472ca

Please sign in to comment.