ASP.NET Core DI lifetimes and captive dependencies: scopes in a C# BackgroundService
Transient: a new instance every time.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | // ====================================================================== // Q: Explain DI lifetimes. What's a captive dependency? // // - Transient: a new instance every time. // - Scoped: one instance per request. This is how DbContext is registered. // - Singleton: one instance for the whole application. // A captive dependency is when a singleton holds a scoped service. A DbContext // then gets shared across threads, even though it is not thread-safe. In // background services you create your own scope. // ====================================================================== // BackgroundService that creates its own scope per run public class PriceSyncWorker(IServiceScopeFactory scopes, ILogger<PriceSyncWorker> log) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken ct) { using var timer = new PeriodicTimer(TimeSpan.FromMinutes(15)); while (await timer.WaitForNextTickAsync(ct)) { await using var scope = scopes.CreateAsyncScope(); var sync = scope.ServiceProvider.GetRequiredService<IPriceSyncService>(); try { await sync.RunAsync(ct); } catch (Exception ex) when (ex is not OperationCanceledException) { log.LogError(ex, "Price sync failed"); // the worker must not die } } } } |