Compze.Threading
0.3.0-alpha
See the version list below for details.
dotnet add package Compze.Threading --version 0.3.0-alpha
NuGet\Install-Package Compze.Threading -Version 0.3.0-alpha
<PackageReference Include="Compze.Threading" Version="0.3.0-alpha" />
<PackageVersion Include="Compze.Threading" Version="0.3.0-alpha" />
<PackageReference Include="Compze.Threading" />
paket add Compze.Threading --version 0.3.0-alpha
#r "nuget: Compze.Threading, 0.3.0-alpha"
#:package Compze.Threading@0.3.0-alpha
#addin nuget:?package=Compze.Threading&version=0.3.0-alpha&prerelease
#tool nuget:?package=Compze.Threading&version=0.3.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 is often incredibly hard to diagnose.
- Deadlocks hang forever bringing production software to a grinding 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 timeout internally. 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 release the lock after the deadlock is resolved its stacktrace will not occur in the exception, but both stack traces will be logged when it does release so the full diagnostic information you need will be there in the logs.
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));
public int GetValue() => _state.Read(state => state.Value); // read lock
public void SetValue(int value) => _state.Update(state => state.Value = value); // update lock + pulse waiters
public int AwaitReady() => _state.ReadWhen(state => state.IsReady, state => state.Value); // wait for condition, then read
public void ProcessWhenReady() => _state.UpdateWhen(state => state.HasWork, state => state.Process()); // wait, then update
public void AwaitDone() => _state.Await(state => state.IsDone); // block until condition is true
}
Lower-Level — IMonitor and IAwaitableMonitor
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);
}
Advanced/rare/risky usages:
class RiskyExample
{
readonly IAwaitableMonitor _monitor = IAwaitableMonitor.WithDefaultTimeout();
public void ReadSomething()
{
...
using(_monitor.TakeReadLock()) { /* read */ }
...
}
public void UpdateSomething()
{
...
using(_monitor.TakeUpdateLock()) { /* update, notifies waiters */ }
...
}
public void WaitThenRead()
{
...
using(_monitor.TakeReadLockWhen(() => ready)) { /* waits for condition, then reads */ }
...
}
}
💡 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.6.0)
- Compze.SystemCE.Internal (>= 0.2.1-alpha)
- Compze.Threading.Internal (>= 0.3.0-alpha)
- Compze.Underscore (>= 0.5.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 |