Compze.Threading
0.9.0-alpha
dotnet add package Compze.Threading --version 0.9.0-alpha
NuGet\Install-Package Compze.Threading -Version 0.9.0-alpha
<PackageReference Include="Compze.Threading" Version="0.9.0-alpha" />
<PackageVersion Include="Compze.Threading" Version="0.9.0-alpha" />
<PackageReference Include="Compze.Threading" />
paket add Compze.Threading --version 0.9.0-alpha
#r "nuget: Compze.Threading, 0.9.0-alpha"
#:package Compze.Threading@0.9.0-alpha
#addin nuget:?package=Compze.Threading&version=0.9.0-alpha&prerelease
#tool nuget:?package=Compze.Threading&version=0.9.0-alpha&prerelease
Compze.Threading
Pit of success threading primitives. Impossible to forget to lock. Automatically resolves and diagnoses deadlocks, including both the involved stack traces in the exception.
The Problem
With the tools built into the BCL:
- Nothing stops you from accessing shared state without locking.
- Forget once and you have an intermittent bug apt to be very hard to diagnose.
- Deadlocks hang forever bringing production software to a permantent halt.
Usage requires constant vigilance. Getting it disastrously wrong is all too easy.
Our Solutions
Automatic Deadlock Resolution with Dual Stack Traces
Every lock acquisition in all the abstractions shown below use a LockTimeout internally when acquiring locks (default 2 minutes). When deadlocks occur the timeout elapses and we capture both stack traces in the exception thrown: the thread that was blocked and the thread that was blocking it. No more guessing which thread held the lock.
💡 If the "winning" thread does not promptly (within 10 seconds) release the lock after the deadlock is resolved its stacktrace will not occur in the exception.
IThreadShared<T> Make Forgetting to Lock Impossible.
class MyThreadSafeClass
{
readonly IThreadShared<PrivateImplementation> _inner = IThreadShared.New(new PrivateImplementation());
void Add(string key, int value) => _inner.Locked(it => it.Add(key, value));
int Get(string key) => _inner.Locked(it => it.Get(key));
class PrivateImplementation{/**/}
}
💡 Note: There is no way to access the PrivateImplementation instance without acquiring the lock. This resolves whole categories of bugs in one fell swoop.
IAwaitableThreadShared<T> — Condition Waits with Read/Update Semantics
To be able to efficiently wait for shared state to reach a certain condition you need IAwaitableThreadShared instead:
class WorkTracker
{
readonly IAwaitableThreadShared<PrivateImplementation> _inner = IAwaitableThreadShared.New(new PrivateImplementation());
void Add(WorkItem item) => _inner.Update(it => it.Add(item));
void Dispose()
{
_inner.UpdateWhen(it => it.IsEmpty, it => it.Dispose());
}
class PrivateImplementation{/**/}
}
💡 Note: Always use
Updateif you change state or are at all unsure. Waiting for conditions will never react to updates made insideRead
API Overview
Simple Locking — IThreadShared<T>
class MyService
{
readonly IThreadShared<MyState> _state = IThreadShared.New(new MyState());
public int GetValue() => _state.Locked(state => state.Value);
public void SetValue(int value) => _state.Locked(state => state.Value = value);
}
One operation: Locked. The shared state is the lambda parameter. No way to misuse it.
Read/Update/Wait — IAwaitableThreadShared<T>
class WorkTracker
{
readonly IAwaitableThreadShared<MyState> _state = IAwaitableThreadShared.WithDefaultTimeouts(new MyState());
// or: IAwaitableThreadShared.New(new MyState(), LockTimeout.Seconds(5), WaitTimeout.Seconds(30));
// Read under lock. Does NOT wake waiting threads.
public int GetValue() => _state.Read(state => state.Value);
// Update under lock and notify all waiting threads to re-evaluate their conditions
public void SetValue(int value) => _state.Update(state => state.Value = value);
// Wait for condition to become true, then read while still holding the lock used for checking the condition
public int AwaitReady() => _state.ReadWhen(state => state.IsReady, state => state.Value);
// Wait for condition to become true, then update while still holding the lock used for checking the condition, then notify all waiting threads about the update before releasing the lock.
public void ProcessWhenReady() => _state.UpdateWhen(state => state.HasWork, state => state.Process());
// Wait for condition to become true
public void AwaitDone() => _state.Await(state => state.IsDone);
}
Lower-Level — IMonitor and IAwaitableMonitor
CRITICAL: The
*Whenmethods (TakeReadLockWhen,TakeUpdateLockWhen,ReadWhen,UpdateWhen,Await, etc.) release the lock while waiting for a condition, just likeMonitor.Wait. When the condition is met, the lock is reacquired before the method returns. This means that if you hold the lock and call a waiting method, other threads can acquire the lock during the wait. AllIAwaitableCriticalSectionimplementations — includingIAwaitableMonitorandIAwaitableMutex— share this behavior.
Wrap all logic in existing public methods inside calls to _monitor to quickly migrate existing classes:
class MyThreadSafe
{
IMonitor _monitor = IMonitor.New();
public void DoStuff() => _monitor.Locked(() =>
{
//implementation here
});
}
class MyAwaitableThreadSafe
{
IAwaitableMonitor _monitor = IAwaitableMonitor.New();
public AType ReadAType() => _monitor.Read(() => _aField);
public void UpdateSomething(AType value) => _monitor.Update(() => _aField = value);
public AType AwaitValue() => _monitor.ReadWhen(() => _aField != null, () => _aField);
}
Lock the entire method body with a using statement. For very hot path code, where even a lambda allocation might be worth caring about.
class MyThreadSafe
{
IMonitor _monitor = IMonitor.New();
public void DoStuff()
{
using var lock = _monitor.TakeLock();
//implementation here
};
}
class MyAwaitableThreadSafe
{
IAwaitableMonitor _monitor = IAwaitableMonitor.New();
public AType ReadAType()
{
using var lock = _monitor.TakeReadLock();
return _aField;
}
public void UpdateSomething(AType value)
{
using var lock = _monitor.TakeUpdateLock();
_aField = value;
}
public AType AwaitValue()
{
using var lock = _monitor.TakeReadLockWhen(() => _aField != null);
return _aField;
}
}
💡 Take*Lock operations are zero allocation, the disposables returned are reused
Lock only in chosen sections of code: Advanced/rare/risky usage:
class RiskyExample
{
readonly IAwaitableMonitor _monitor = IAwaitableMonitor.WithDefaultTimeout();
public void ReadSomething()
{
//Unsynchronized code
using(_monitor.TakeReadLock()) { /* read */ }
//Unsynchronized code
}
public void UpdateSomething()
{
//Unsynchronized code
using(_monitor.TakeUpdateLock()) { /* update, notifies waiters */ }
//Unsynchronized code
}
public void WaitThenRead()
{
//Unsynchronized code
using(_monitor.TakeReadLockWhen(() => ready)) { /* waits for condition, then reads */ }
//Unsynchronized code
}
}
💡 WARNING: Think carefully before doing this. Much of the problems with BCL primitives come right back. Profile before you assume that more granular locking is needed for performance. It is far more rare than you may think.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Compze.Contracts (>= 0.7.1)
- Compze.Underscore (>= 0.6.0-beta)
NuGet packages (21)
Showing the top 5 NuGet packages that depend on Compze.Threading:
| Package | Downloads |
|---|---|
|
Compze.DependencyInjection
Package Description |
|
|
Compze.Internals.SystemCE
For Compze internal use only. Do not take a direct dependency on this package. |
|
|
Compze.Abstractions
Core abstractions for the Compze framework: entity IDs, message type contracts, time sources, serialization interfaces, and type mapping infrastructure. |
|
|
Compze.Tessaging
Messaging infrastructure for the Compze framework including command, query, and event handling. |
|
|
Compze.Sql.Common
For Compze internal use only. Do not take a direct dependency on this package |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.9.0-alpha | 308 | 7/24/2026 |
| 0.8.0-alpha | 247 | 7/15/2026 |
| 0.7.0-alpha | 239 | 7/5/2026 |
| 0.6.0-alpha | 105 | 7/4/2026 |
| 0.5.0-alpha | 708 | 6/4/2026 |
| 0.3.0-alpha | 91 | 3/4/2026 |