Skip to content

Latest commit

 

History

History
145 lines (117 loc) · 4.2 KB

threadsafe-off-for-single-thread-composition.md

File metadata and controls

145 lines (117 loc) · 4.2 KB

ThreadSafe Off for single-thread composition

Pure.DI generates thread-safe code by default because composition instances are often shared. When a composition is created and used on one thread, such as inside a command-line import step, a game-loop setup phase, or a short-lived benchmark harness, the synchronization path can be disabled explicitly. This example builds a report import pipeline that is created, used, and discarded inside one job. ThreadSafe = Off removes generated locking for composition-owned cached instances, while the application keeps the single-threaded ownership rule at the boundary.

using Shouldly;
using Pure.DI;
using static Pure.DI.Hint;
using static Pure.DI.Lifetime;

DI.Setup(nameof(Composition))
    .Hint(ThreadSafe, "Off")
    .Bind().As(Singleton).To<ImportCache>()
    .Bind<IImportJob>().To<ImportJob>()
    .Root<IImportJob>("Job");

var composition = new Composition();
var job = composition.Job;

job.Import(["A-100", "A-100", "B-200"]).ShouldBe(2);

interface IImportJob
{
    int Import(IReadOnlyList<string> productCodes);
}

sealed class ImportJob(ImportCache cache) : IImportJob
{
    public int Import(IReadOnlyList<string> productCodes)
    {
        foreach (var code in productCodes)
        {
            cache.SeenCodes.Add(code);
        }

        return cache.SeenCodes.Count;
    }
}

sealed class ImportCache
{
    public HashSet<string> SeenCodes { get; } = [];
}
Running this code sample locally
dotnet --list-sdk
  • Create a net10.0 (or later) console application
dotnet new console -n Sample
dotnet add package Pure.DI
dotnet add package Shouldly
  • Copy the example code into the Program.cs file

You are ready to run the example 🚀

dotnet run

Use this only when the composition instance is not shared across threads. If delegates, factories, or roots can be invoked concurrently, keep thread safety enabled or synchronize the critical factory section yourself with ctx.Lock.

The legitimate cases are narrow and easy to check by eye: a command-line run, a single-threaded setup phase before workers start, a benchmark harness, and unit tests that create their own composition per test. The generated code drops the lock around cached singletons, which is measurable only when those singletons are resolved in a tight loop.

Warning

A composition created on one thread but reached from a Task, a timer callback or a UI dispatcher is not single-threaded. The failure mode is a torn or duplicated singleton that appears under load, not at startup.

Prefer the default thread-safe generation for anything that lives longer than one call stack. If only one factory needs protection, keep ThreadSafe on and guard that factory with ctx.Lock - see Factory with thread synchronization and ThreadSafe hint.

The following partial class will be generated
partial class Composition
{

  private ImportCache? _singletonWcfLikeChannel;

  public IImportJob Job
  {
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    get
    {
      if (_singletonWcfLikeChannel is null)
      {
        _singletonWcfLikeChannel = new ImportCache();
      }

      return new ImportJob(_singletonWcfLikeChannel);
    }
  }
}

Class diagram:

---
 config:
  class:
   hideEmptyMembersBox: true
---
classDiagram
	ImportJob --|> IImportJob
	Composition ..> ImportJob : IImportJob Job
	ImportJob o-- "Singleton" ImportCache : ImportCache
	namespace Pure.DI.UsageTests.HighPerformance.ThreadSafeOffScenario {
		class Composition {
		<<partial>>
		+IImportJob Job
		}
		class IImportJob {
			<<interface>>
		}
		class ImportCache {
				<<class>>
			+ImportCache()
		}
		class ImportJob {
				<<class>>
			+ImportJob(ImportCache cache)
		}
	}
Loading

See also: