Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ca0b766
- Added AsyncMutexLock.cs
simonegli8 Jun 5, 2026
f6f5d73
Fixes in AsyncLock.cs
simonegli8 Jun 5, 2026
eb0d96d
Use of File lock on Windows, no Mutex, since it does not work for async.
simonegli8 Jun 5, 2026
86f6a89
Working version
simonegli8 Jun 6, 2026
ff85895
Added MutexRelease to ProcessExit
simonegli8 Jun 6, 2026
b361e03
Migrated AsyncLock.sln to AsyncLock.slnx
simonegli8 Jun 6, 2026
321b65f
Re added support for netstandard1.3. AsnyMutexLock does not support n…
simonegli8 Jun 6, 2026
903c7f9
Reverted to NeoSmart Company name & PackageId
simonegli8 Jun 6, 2026
dd1a0f8
Removed Simon Egli from Authors
simonegli8 Jun 6, 2026
c887d3c
Corrected Assert.AreEqual parameter order
simonegli8 Jun 6, 2026
2156007
Reverted namespace
simonegli8 Jun 6, 2026
0b1eb4a
Fix
simonegli8 Jun 6, 2026
1cf36ff
Fix
simonegli8 Jun 6, 2026
f1fa01d
Fix
simonegli8 Jun 6, 2026
710418d
Support AsyncMutexLock on netstandard1.3
simonegli8 Jun 6, 2026
725e55b
Fix
simonegli8 Jun 6, 2026
3572336
Bugfix in MixedSyncAsyncTimeout.cs
simonegli8 Jun 6, 2026
b522f41
Bugfix
simonegli8 Jun 6, 2026
de0b7a0
Bugfix
simonegli8 Jun 6, 2026
1c05ba6
Fixed README
simonegli8 Jun 6, 2026
e075c5a
Fix
simonegli8 Jun 6, 2026
c902f25
Bugfix
simonegli8 Jun 6, 2026
7240399
USe assembly name as prefix for name
simonegli8 Jun 7, 2026
e2a1ef2
Fix typoo
simonegli8 Jun 7, 2026
e6fe865
Bugfix
simonegli8 Jun 7, 2026
0799a4f
Bugfix
simonegli8 Jun 7, 2026
bb5d31b
Aktualisieren von AsyncLock.csproj
simonegli8 Jun 7, 2026
f47cc99
Bugfix in AsyncMutexLock.NormalizeName
simonegli8 Jun 7, 2026
9e951ed
Merge branch 'master' of https://github.com/simonegli8/AsyncLock
simonegli8 Jun 7, 2026
c2db04e
Renamed assembly to NeoSmart.AsyncLock
simonegli8 Jun 7, 2026
bc1a694
Bugfixes, add LockFileName
simonegli8 Jun 7, 2026
5dd99b0
Made FileName public
simonegli8 Jun 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,5 @@ paket-files/
# JetBrains Rider
.idea/
*.sln.iml

NugetApiKey.txt
31 changes: 0 additions & 31 deletions AsyncLock.sln

This file was deleted.

4 changes: 4 additions & 0 deletions AsyncLock.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<Solution>
<Project Path="AsyncLock/AsyncLock.csproj" />
<Project Path="UnitTests/UnitTests.csproj" />
</Solution>
49 changes: 42 additions & 7 deletions AsyncLock/AsyncLock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class AsyncLock
internal SemaphoreSlim _retry = new SemaphoreSlim(0, 1);
private const long UnlockedId = 0x00; // "owning" task id when unlocked
internal long _owningId = UnlockedId;
internal int _owningThreadId = (int) UnlockedId;
internal int _owningThreadId = (int)UnlockedId;
private static long AsyncStackCounter = 0;
// An AsyncLocal<T> is not really the task-based equivalent to a ThreadLocal<T>, in that
// it does not track the async flow (as the documentation describes) but rather it is
Expand Down Expand Up @@ -93,7 +93,8 @@ internal async Task<IDisposable> ObtainLockAsync(CancellationToken cancellationT
// In case of zero-timeout, don't even wait for protective lock contention
if (timeout == TimeSpan.Zero)
{
_parent._reentrancy.Wait(timeout);
//BUG? _parent._reentrancy.Wait(timeout);
if (!_parent._reentrancy.Wait(timeout)) return null;
if (InnerTryEnter(synchronous: false))
{
// Reset the owning thread id after all await calls have finished, otherwise we
Expand All @@ -113,7 +114,8 @@ internal async Task<IDisposable> ObtainLockAsync(CancellationToken cancellationT
// We need to wait for someone to leave the lock before trying again.
while (remainder > TimeSpan.Zero)
{
await _parent._reentrancy.WaitAsync(remainder).ConfigureAwait(false);
//BUG? await _parent._reentrancy.WaitAsync(remainder).ConfigureAwait(false);
if (!await _parent._reentrancy.WaitAsync(remainder).ConfigureAwait(false)) return null;
if (InnerTryEnter(synchronous: false))
{
// Reset the owning thread id after all await calls have finished, otherwise we
Expand All @@ -122,12 +124,14 @@ internal async Task<IDisposable> ObtainLockAsync(CancellationToken cancellationT
_parent._reentrancy.Release();
return this;
}
_parent._reentrancy.Release();
//BUG? _parent._reentrancy.Release();

now = DateTimeOffset.UtcNow;
remainder -= now - last;
last = now;
if (remainder < TimeSpan.Zero)
//BUG? if (remainder < TimeSpan.Zero)
// <= is correct, cause the loop invariant is remainder > TimeSpan.Zero, and the need to release reentrnacy
if (remainder <= TimeSpan.Zero)
{
_parent._reentrancy.Release();
return null;
Expand Down Expand Up @@ -202,7 +206,8 @@ internal IDisposable ObtainLock(CancellationToken cancellationToken = default)
// In case of zero-timeout, don't even wait for protective lock contention
if (timeout == TimeSpan.Zero)
{
_parent._reentrancy.Wait(timeout);
//BUG? _parent._reentrancy.Wait(timeout);
if (!_parent._reentrancy.Wait(timeout)) return null;
if (InnerTryEnter(synchronous: true))
{
_parent._reentrancy.Release();
Expand All @@ -219,7 +224,8 @@ internal IDisposable ObtainLock(CancellationToken cancellationToken = default)
// We need to wait for someone to leave the lock before trying again.
while (remainder > TimeSpan.Zero)
{
_parent._reentrancy.Wait(remainder);
//BUG? _parent._reentrancy.Wait(remainder);
if (!_parent._reentrancy.Wait(remainder)) return null;
if (InnerTryEnter(synchronous: true))
{
_parent._reentrancy.Release();
Expand Down Expand Up @@ -498,5 +504,34 @@ public bool TryLock(Action callback, TimeSpan timeout)
}
return true;
}

public Task<IDisposable> LockAsync(TimeSpan timeout)
{
var @lock = new InnerLock(this, _asyncId.Value, ThreadId);
_asyncId.Value = Interlocked.Increment(ref AsyncLock.AsyncStackCounter);

return @lock.TryObtainLockAsync(timeout)
.ContinueWith(state =>
{
if (state.Exception is AggregateException ex)
{
ExceptionDispatchInfo.Capture(ex.InnerException!).Throw();
}
var disposableLock = state.Result;
if (disposableLock is null) throw new TimeoutException("LockAsync timed out.");
return disposableLock;
});
}

public IDisposable Lock(TimeSpan timeout)
{
var @lock = new InnerLock(this, _asyncId.Value, ThreadId);
// Increment the async stack counter to prevent a child task from getting
// the lock at the same time as a child thread.
_asyncId.Value = Interlocked.Increment(ref AsyncLock.AsyncStackCounter);
var lockDisposable = @lock.TryObtainLock(timeout);
if (lockDisposable is null) throw new TimeoutException("TryLock timed out.");
return lockDisposable;
}
}
}
12 changes: 9 additions & 3 deletions AsyncLock/AsyncLock.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.1</TargetFrameworks>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks>
<AssemblyName>NeoSmart.AsyncLock</AssemblyName>
<RootNamespace>NeoSmart.AsyncLock</RootNamespace>
<PackageId>NeoSmart.AsyncLock</PackageId>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
Expand All @@ -15,7 +16,7 @@
<PackageProjectUrl>https://neosmart.net/blog/2017/asynclock-an-asyncawait-friendly-locking-library-for-c-and-net/</PackageProjectUrl>
<RepositoryUrl>https://github.com/neosmart/AsyncLock</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>asynclock, async await, async, await, lock, synchronization</PackageTags>
<PackageTags>asynclock, async await, async, await, lock, synchronization, mutex, async mutex</PackageTags>
<PackageReleaseNotes>
3.2: New TryLock() and TryLockAsync() methods, CancellationToken support for synchronous locking routines.

Expand Down Expand Up @@ -43,4 +44,9 @@
</None>
</ItemGroup>

<ItemGroup Condition="$(TargetFramework) == 'netstandard1.3'">
<PackageReference Include="System.Threading.Thread" Version="4.3.0" />
<PackageReference Include="System.Runtime.Extensions" Version="4.3.0" />
</ItemGroup>

</Project>
Loading