[英]Create scope using IServiceProvider from singleton instance
假設我有自己的 class QueueListener<TService, TPayload>
繼承自BackgroundService
。 它打開 TCP 連接並監聽傳入的消息。 在每條消息上,我想初始化TService
類型的服務,並將 TPayload 的TPayload
實例反序列化傳遞給它。 TService
將被注冊為 Transient,因此它意味着輕量級和無狀態,因為有效載荷的處理程序必須是(在我當前的任務中)。 為此,我將在QueueListener
的構造函數中注入IServiceProvider
,並在它收到的每條消息上創建一個 scope。 這聽起來像是一個計划還是我過度設計了? 我想避免TService
也是 singleton。
文件說:
從 singleton 解析一個 scoped service 是很危險的,它可能會導致服務在處理后續請求時有不正確的 state。
但我不完全確定這是什么意思。 無法在BackgroundService
中注入作用域服務,因為它的生命周期為 Singleton。 他們警告我不要像我一樣做事嗎?
更新 #1
我解釋了為什么我想在每條消息上創建 scope。 這背后的想法是防止監聽器被消息處理阻塞,並為其他開發人員提供創建自己的處理程序並對接收到的消息做一些事情的可能性。 例如,其他開發人員可以在處理時創建數據庫連接,我希望它在處理完成后關閉並釋放。
TService
注冊為作用域,並為每條消息創建一個新的 scope。 然后從創建的 scope 中解析TService
。 只需閱讀在后台任務中使用范圍服務
你可以這樣寫:
services.AddHostedService<MyBackgroundService>();
services.AddScoped<IScopedServicePerMessage, ScopedServicePerMessage>();
...
public class MyBackgroundService : BackgroundService
{
private readonly IServiceProvider _sp;
public MyBackgroundService(IServiceProvider sp)
{
_sp = sp;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
DoWork(stoppingToken);
return Task.CompletedTask;
}
private void DoWork(CancellationToken stoppingToken)
{
while(true)
{
var msg = GetNextMessage();
using (var scope = _sp.CreateScope())
{
var servicePerMessage = scope.ServiceProvider.GetRequiredService<IScopedServicePerMessage>();
servicePerMessage.Handle(msg);
}
}
}
...
}
關於這一點:
從 singleton 解析范圍服務是很危險的。 可能會導致服務在處理后續請求時出現錯誤的state。
這是關於將作用域服務(例如,ef core dbcontext)直接注入 singleton 的情況。 這不是你的情況。
該文檔是指將范圍服務注入 singleton 服務。 由於注入發生在 singleton object 的構造中,因此將在那時提供范圍服務。 這將有效地將作用域服務的生命周期延長到 singleton 服務的生命周期。 這是危險的,因為通常會明確選擇范圍服務生命周期以確保 object 再次被快速處置。
最常見的例子是擁有數據庫連接的數據庫上下文; 您要確保盡快釋放此數據庫連接以釋放資源。 但是,如果您將上下文注入到 singleton 服務中,它將永遠不會被釋放。
然而,這並不意味着無法在 singleton 服務中使用范圍服務。 這是通過讓 singleton 服務創建一個服務 scope 來完成的,然后它可以從中檢索 singleton 服務。 重要的是,此服務 scope 應該是短暫的。 因此,以 ASP.NET 核心本身為例,其中為每個請求創建一個服務 scope,並執行類似的操作。 例如,在您的情況下,如果這對您的應用程序有意義,您可以為每條傳入消息執行此操作。
要創建服務 scope,您應該注入一個IServiceScopeFactory
; 然后,您可以使用它創建一個 scope,如下所示:
public async Task Process(TPayload payload)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var service = scope.GetService<TService>();
await service.Process(payload);
}
}
僅當您需要使用范圍服務時,才嚴格需要此模式。 您可以直接解析所有其他服務,而無需創建 scope。 如果您可以重用相同的服務實例來處理所有有效負載,您還可以將服務作為 singleton 注入(與將其注冊為瞬態但僅解析一次相同)。 如果您需要為每個有效負載創建一個新實例,請考慮創建 scope,即使它不是絕對必要的。
首先, 瞬態服務不是范圍服務。 瞬態服務通常由您的代碼在外部擁有,並在每次從容器中解析時創建。 容器不緩存瞬態服務。
TService
將被注冊為 Transient... 為此,我將在我的QueueListener
的構造函數中注入IServiceProvider
並在它收到的每條消息上創建一個 scope。
您不需要 scope 來解決瞬態服務。 即使您創建了 scope,scope 仍然不管理/擁有瞬態服務。 例如,結束 scope 的生命周期不會結束瞬態服務的生命周期。
您可以簡單地使用在QueueListener
中注入的IServiceProvider
來解析TService
。 並且每個TService
解析應該已經像你想要的那樣
輕量級和無狀態作為有效負載的處理程序
關於
文檔說:
由於您沒有使用范圍服務,因此該文檔所說的內容現在可能不相關。 但如果你想知道原因:
從 singleton 解析范圍服務是很危險的。
Singleton是一種特殊的scope。 Singleton 服務在容器的“根”scope 中創建和緩存,該容器本質上是容器本身。
如果您從 singleton 解析范圍服務,則解析和緩存服務實例的生存期 / scope 可能是“根” scope。 這導致了一個問題,即作用域服務實例被緩存在容器內,並在多個客戶端請求之間共享。
這是危險的,因為范圍服務應該是
每個客戶端請求(連接)創建一次范圍生命周期服務 (AddScoped)。
我不希望我的 singleton class 直接依賴於 IServiceProvider。 所以我使用了一個定制工廠來實現這個目標。 願此代碼示例對其他人有所幫助:
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IScopedBar, ScopedBar>();
services.AddSingleton<IScopedServiceFactory<IScopedBar>, ScopedServiceFactory<IScopedBar>>(
(provider) => {
var scope = provider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IScopedBar>();
return new ScopedServiceFactory<IScopedBar>(() => new ScopedService<IScopedBar>(scope, service));
});
services.AddSingleton<ISingletonFoo, SingletonFoo>();
}
// ...
}
public interface ISingletonFoo
{
void DoSomethingUsingScopedServices();
}
public class SingletonFoo : ISingletonFoo
{
private readonly IScopedServiceFactory<IScopedBar> _barFactory;
public SingletonFoo(IScopedServiceFactory<IScopedBar> barFactory)
{
_barFactory = barFactory;
}
public void DoSomethingUsingScopedServices()
{
using var scopedService = _barFactory.CreateService();
scopedService.Service.DoSomething();
}
}
public interface IScopedBar
{
void DoSomething();
}
public class ScopedBar : IScopedBar
{
public void DoSomething()
{
// Do something
}
}
public interface IScopedService<T> : IDisposable
{
T Service { get; }
}
public interface IScopedServiceFactory<T>
{
IScopedService<T> CreateService();
}
public class ScopedService<T> : IScopedService<T>
{
private readonly IDisposable _scope;
public ScopedService(IDisposable scope, T service)
{
_scope = scope;
Service = service;
}
public T Service { get; }
public void Dispose()
{
_scope.Dispose();
}
}
public class ScopedServiceFactory<T> : IScopedServiceFactory<T>
{
private readonly Func<IScopedService<T>> _serviceFactory;
public ScopedServiceFactory(Func<IScopedService<T>> serviceFactory)
{
_serviceFactory = serviceFactory;
}
public IScopedService<T> CreateService()
{
return _serviceFactory();
}
}
這是我使用作用域服務的代碼:
public interface IScopedResolver<T> where T: class
{
TResult Resolve<TResult>(Func<T, TResult> dataFactory);
Task<TResult> ResolveAsync<TResult>(Func<T, Task<TResult>> dataFactory);
}
執行 class:
public class ScopedResolver<T> : IScopeResolver<T> where T: class
{
private readonly IServiceProvider _provider;
public ScopedResolver(IServiceProvider provider)
{
_provider = provider;
}
public TResult Resolve<TResult>(Func<T, TResult> dataFactory)
{
using IServiceScope scope = _provider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<T>();
return dataFactory(service);
}
public async Task<TResult> ResolveAsync<TResult>(Func<T, Task<TResult>> dataFactory)
{
using var scope = _provider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<T>();
return await dataFactory(service);
}
}
啟動時注冊:
services.AddSingleton(typeof(IScopedResolver<>), typeof(ScopedResolver<>));
使用 ScopedResolve:
public class ServiceA
{
private readonly IScopedResolver<DbContext> _context;
public ServiceA(IScopedResolver<DbContext> context)
{
_context = context;
}
public async Task<List<ClassOne>> GetListAsync()
{
return await _context.ResolveAsync(async s => await s.Set<ClassOne>().ToListAsync());
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.