Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ A simple C# repository containing a few basic useful Thread-Safe Objects.
- DataObject
- Extentions
- IBindableObjectExtention
- IMethodThreadLocksExtentions
- INotifyCollectionChangedExtension
- INotifyPropertyChangedExtension
- ISingleThreadLockExtentions
- ObjectExtention
- HelperClasses
- ThreadHelper
Expand All @@ -64,8 +66,8 @@ A simple C# repository containing a few basic useful Thread-Safe Objects.
- IHashSetThreadSafe
- ILinkedListThreadSafe
- IListThreadSafe
- IMethodThreadLock
- IMultipleThreadLockManager\<TKey, TValue>
- IMethodThreadLocks
- IMultipleThreadLocks\<TKey>
- IObservableCollectionThreadSafe
- IObservableCollectionThreadSafe\<T>
- IObservableDataCollection
Expand All @@ -87,8 +89,8 @@ A simple C# repository containing a few basic useful Thread-Safe Objects.
- Locks
- GatedThreadLock
- GateKeeper
- MethodThreadLock
- MultipleThreadLockManager
- MethodThreadLocks
- MultipleThreadLocks
- SingleThreadLock

----
Expand Down Expand Up @@ -454,6 +456,17 @@ This can be overwritten durring creation or by setting Property `WaitOnNotifyCol
Observable Objects Property `WaitOnNotifyPropertyChanged` has been renamed to Property `WaitOnNotifying`.

Observable Collections Property `WaitOnNotifyCollectionChanged` has been removed and now uses Property `WaitOnNotifying`.


## Breaking changes from v3.1.1 to v3.1.2!

Lock `MethodThreadLock` has been renamed to `MethodThreadLocks`.

Interface `IMethodThreadLock` has been renamed to `IMethodThreadLocks`.

Lock `MultipleThreadLockManager` has been renamed to `MultipleThreadLocks`.

Interface `IMultipleThreadLockManager` has been renamed to `IMultipleThreadLocks`.
----


Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,44 @@

namespace ThunderDesign.Net_PCL.Threading.UnitTests.Locks
{
public class MethodThreadLockTests
public class MethodThreadLocksTests
{
private async Task MethodA(MethodThreadLock methodLock, int delayMs)
private async Task MethodA(MethodThreadLocks methodLocks, int delayMs)
{
await methodLock.LockAsync();
await methodLocks.LockAsync();
try
{
await Task.Delay(delayMs);
}
finally
{
methodLock.Unlock();
methodLocks.Unlock();
}
}

private async Task MethodB(MethodThreadLock methodLock, int delayMs)
private async Task MethodB(MethodThreadLocks methodLocks, int delayMs)
{
await methodLock.LockAsync();
await methodLocks.LockAsync();
try
{
await Task.Delay(delayMs);
}
finally
{
methodLock.Unlock();
methodLocks.Unlock();
}
}

[Fact]
public async Task LockAsync_ShouldUseCallerMemberName_ToScopeLock()
{
using var methodLock = new MethodThreadLock();
using var methodLocks = new MethodThreadLocks();

var firstCall = MethodA(methodLock, 200);
var firstCall = MethodA(methodLocks, 200);

await Task.Delay(20); // allow first call to acquire lock

var secondCall = MethodA(methodLock, 20);
var secondCall = MethodA(methodLocks, 20);

var completedEarly = await Task.WhenAny(secondCall, Task.Delay(50)) == secondCall;
Assert.False(completedEarly);
Expand All @@ -54,10 +54,10 @@ public async Task LockAsync_ShouldUseCallerMemberName_ToScopeLock()
[Fact]
public async Task LockAsync_ShouldNotBlock_DifferentMethods()
{
using var methodLock = new MethodThreadLock();
using var methodLocks = new MethodThreadLocks();

var callA = MethodA(methodLock, 200);
var callB = MethodB(methodLock, 200);
var callA = MethodA(methodLocks, 200);
var callB = MethodB(methodLocks, 200);

var completed = await Task.WhenAny(Task.WhenAll(callA, callB), Task.Delay(500));

Expand All @@ -67,44 +67,44 @@ public async Task LockAsync_ShouldNotBlock_DifferentMethods()
[Fact]
public async Task LockAsync_WithExplicitMethodName_ShouldScopeIndependently()
{
using var methodLock = new MethodThreadLock();
using var methodLocks = new MethodThreadLocks();

await methodLock.LockAsync("CustomKey1");
await methodLocks.LockAsync("CustomKey1");

var otherKeyTask = methodLock.LockAsync("CustomKey2");
var otherKeyTask = methodLocks.LockAsync("CustomKey2");
var completed = await Task.WhenAny(otherKeyTask, Task.Delay(500)) == otherKeyTask;

Assert.True(completed);

methodLock.Unlock("CustomKey1");
methodLock.Unlock("CustomKey2");
methodLocks.Unlock("CustomKey1");
methodLocks.Unlock("CustomKey2");
}

[Fact]
public async Task LockAsync_ShouldThrowArgumentNullException_WhenMethodNameIsNullOrWhiteSpace()
{
using var methodLock = new MethodThreadLock();
using var methodLocks = new MethodThreadLocks();

await Assert.ThrowsAsync<ArgumentNullException>(() => methodLock.LockAsync(" "));
await Assert.ThrowsAsync<ArgumentNullException>(() => methodLocks.LockAsync(" "));
}

[Fact]
public void Unlock_ShouldThrowArgumentNullException_WhenMethodNameIsNullOrWhiteSpace()
{
using var methodLock = new MethodThreadLock();
using var methodLocks = new MethodThreadLocks();

Assert.Throws<ArgumentNullException>(() => methodLock.Unlock(" "));
Assert.Throws<ArgumentNullException>(() => methodLocks.Unlock(" "));
}

[Fact]
public async Task LockAsync_ShouldThrowOperationCanceledException_WhenCancelled()
{
using var methodLock = new MethodThreadLock();
using var methodLocks = new MethodThreadLocks();

await methodLock.LockAsync(nameof(LockAsync_ShouldThrowOperationCanceledException_WhenCancelled));
await methodLocks.LockAsync(nameof(LockAsync_ShouldThrowOperationCanceledException_WhenCancelled));

using var cts = new CancellationTokenSource();
var waitingTask = methodLock.LockAsync(nameof(LockAsync_ShouldThrowOperationCanceledException_WhenCancelled), cts.Token);
var waitingTask = methodLocks.LockAsync(nameof(LockAsync_ShouldThrowOperationCanceledException_WhenCancelled), cts.Token);

cts.Cancel();

Expand All @@ -114,27 +114,27 @@ public async Task LockAsync_ShouldThrowOperationCanceledException_WhenCancelled(
[Fact]
public async Task LockAsync_ShouldThrowObjectDisposedException_WhenDisposedWhileWaiting()
{
var methodLock = new MethodThreadLock();
var methodLocks = new MethodThreadLocks();

await methodLock.LockAsync("SharedMethod");
await methodLocks.LockAsync("SharedMethod");

var waitingTask = methodLock.LockAsync("SharedMethod");
methodLock.Dispose();
var waitingTask = methodLocks.LockAsync("SharedMethod");
methodLocks.Dispose();

await Assert.ThrowsAsync<ObjectDisposedException>(() => waitingTask);
}

[Fact]
public async Task LockAsync_ShouldOnlyAllowOneCaller_PerMethodName_UnderConcurrentAccess()
{
using var methodLock = new MethodThreadLock();
using var methodLocks = new MethodThreadLocks();
var counter = 0;
var maxObservedConcurrency = 0;
var gate = new object();

async Task Worker()
{
await methodLock.LockAsync(nameof(Worker));
await methodLocks.LockAsync(nameof(Worker));
try
{
lock (gate)
Expand All @@ -152,7 +152,7 @@ async Task Worker()
}
finally
{
methodLock.Unlock(nameof(Worker));
methodLocks.Unlock(nameof(Worker));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,31 @@ public class MultipleThreadLockManagerTests
[Fact]
public async Task LockAsync_ShouldThrowArgumentNullException_WhenKeyIsNull()
{
using var manager = new MultipleThreadLockManager<string, SingleThreadLock>();
using var manager = new MultipleThreadLocks<string, SingleThreadLock>();

await Assert.ThrowsAsync<ArgumentNullException>(() => manager.LockAsync(null!));
}

[Fact]
public void Unlock_ShouldThrowArgumentNullException_WhenKeyIsNull()
{
using var manager = new MultipleThreadLockManager<string, SingleThreadLock>();
using var manager = new MultipleThreadLocks<string, SingleThreadLock>();

Assert.Throws<ArgumentNullException>(() => manager.Unlock(null!));
}

[Fact]
public void Unlock_ShouldThrowKeyNotFoundException_WhenKeyDoesNotExist()
{
using var manager = new MultipleThreadLockManager<string, SingleThreadLock>();
using var manager = new MultipleThreadLocks<string, SingleThreadLock>();

Assert.Throws<KeyNotFoundException>(() => manager.Unlock("missing-key"));
}

[Fact]
public async Task Unlock_ShouldThrowSynchronizationLockException_WhenKeyLockIsNotHeld()
{
using var manager = new MultipleThreadLockManager<string, SingleThreadLock>();
using var manager = new MultipleThreadLocks<string, SingleThreadLock>();

await manager.LockAsync("key1");
manager.Unlock("key1");
Expand All @@ -47,7 +47,7 @@ public async Task Unlock_ShouldThrowSynchronizationLockException_WhenKeyLockIsNo
[Fact]
public async Task LockAsync_ShouldCreateSeparateLocks_ForDifferentKeys()
{
using var manager = new MultipleThreadLockManager<string, SingleThreadLock>();
using var manager = new MultipleThreadLocks<string, SingleThreadLock>();

var lockTask1 = manager.LockAsync("key1");
var lockTask2 = manager.LockAsync("key2");
Expand All @@ -61,7 +61,7 @@ public async Task LockAsync_ShouldCreateSeparateLocks_ForDifferentKeys()
[Fact]
public async Task LockAsync_ShouldBlock_ForSameKey_UntilUnlocked()
{
using var manager = new MultipleThreadLockManager<string, SingleThreadLock>();
using var manager = new MultipleThreadLocks<string, SingleThreadLock>();

await manager.LockAsync("shared-key");

Expand All @@ -79,7 +79,7 @@ public async Task LockAsync_ShouldBlock_ForSameKey_UntilUnlocked()
[Fact]
public async Task LockAsync_ShouldThrowOperationCanceledException_WhenCancelled()
{
using var manager = new MultipleThreadLockManager<string, SingleThreadLock>();
using var manager = new MultipleThreadLocks<string, SingleThreadLock>();

await manager.LockAsync("key1");

Expand All @@ -94,7 +94,7 @@ public async Task LockAsync_ShouldThrowOperationCanceledException_WhenCancelled(
[Fact]
public async Task LockAsync_ShouldThrowObjectDisposedException_WhenManagerIsDisposedWhileWaiting()
{
var manager = new MultipleThreadLockManager<string, SingleThreadLock>();
var manager = new MultipleThreadLocks<string, SingleThreadLock>();

await manager.LockAsync("key1");

Expand All @@ -108,7 +108,7 @@ public async Task LockAsync_ShouldThrowObjectDisposedException_WhenManagerIsDisp
[Fact]
public async Task LockAsync_ShouldThrowObjectDisposedException_WhenAlreadyDisposed()
{
var manager = new MultipleThreadLockManager<string, SingleThreadLock>();
var manager = new MultipleThreadLocks<string, SingleThreadLock>();
manager.Dispose();

await Assert.ThrowsAsync<ObjectDisposedException>(() => manager.LockAsync("key1"));
Expand All @@ -117,7 +117,7 @@ public async Task LockAsync_ShouldThrowObjectDisposedException_WhenAlreadyDispos
[Fact]
public void Unlock_ShouldThrowObjectDisposedException_WhenAlreadyDisposed()
{
var manager = new MultipleThreadLockManager<string, SingleThreadLock>();
var manager = new MultipleThreadLocks<string, SingleThreadLock>();
manager.Dispose();

Assert.Throws<ObjectDisposedException>(() => manager.Unlock("key1"));
Expand All @@ -126,7 +126,7 @@ public void Unlock_ShouldThrowObjectDisposedException_WhenAlreadyDisposed()
[Fact]
public void Dispose_ShouldBeIdempotent()
{
var manager = new MultipleThreadLockManager<string, SingleThreadLock>();
var manager = new MultipleThreadLocks<string, SingleThreadLock>();

manager.Dispose();
var exception = Record.Exception(() => manager.Dispose());
Expand All @@ -137,7 +137,7 @@ public void Dispose_ShouldBeIdempotent()
[Fact]
public async Task LockAsync_ShouldOnlyAllowOneThreadPerKey_UnderConcurrentAccess()
{
using var manager = new MultipleThreadLockManager<string, SingleThreadLock>();
using var manager = new MultipleThreadLocks<string, SingleThreadLock>();
var counter = 0;
var maxObservedConcurrency = 0;
var gate = new object();
Expand Down
4 changes: 4 additions & 0 deletions src/ThunderDesign.Net-PCL.Threading.nuspec.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<releaseNotes>See ${{ env.REPOSITORY_URL }}/releases/tag/$version$</releaseNotes>
<tags>${{ env.TAGS }}</tags>
<repository type="git" url="${{ env.REPOSITORY_URL }}" />
<icon>icon.png</icon>
<readme>README.md</readme>
<dependencies>
<!-- Explicitly declare empty groups for netstandard1.x so they don't inherit the Toolkit --> <group targetFramework="netstandard1.0" />
Expand Down Expand Up @@ -58,6 +59,9 @@
<!-- The global Analyzer folder (safely guarded by csproj targeting limits) -->
<file src="src/ThunderDesign.Net-PCL.SourceGenerators/bin/Release/netstandard2.0/ThunderDesign.Net-PCL.SourceGenerators.dll" target="analyzers/dotnet/cs/" />

<!-- Include the icon -->
<file src="..\icon.png" target="" />

<!-- Include the README -->
<file src="README.md" target="" />
</files>
Expand Down
Loading
Loading