.NET Core DI - 在控制台应用程序中处理单例服务
.NET Core DI - disposing of Singleton service in Console App
我有这个小型控制台应用程序,我决定为其连接 Microsoft 的 DI,以便它为我处理依赖项。我没有那么多范围,而是瞬态和单例服务。
问题是,它不处理单例服务。也许是因为它不知道我什么时候完成它?可能是其他原因吧。
这是我的静态 class 处理注入的代码(显然在最终应用程序中它有点复杂)
public static class ServiceInjector
{
static IServiceProvider _services;
static Stack<IServiceScope> _scopeStack;
static IServiceProvider _provider => _scopeStack.NullPeek()?.ServiceProvider ?? _services;
public static void Configure()
{
var svcCollection = new ServiceCollection();
svcCollection.AddSingleton<ISampleSingletonService, SampleSingletonService>();
_services = svcCollection.BuildServiceProvider();
_scopeStack = new Stack<IServiceScope>();
}
public static T GetService<T>()
{
return _provider.GetService<T>();
}
public static void StartScope()
{
_scopeStack.Push(_services.CreateScope());
}
public static void KillAllScopes()
{
while(_scopeStack.TryPop(out IServiceScope scope))
{
scope.Dispose();
}
_services = null; // I also tried to convince GC to clean it up and force Dispose. Nope.
}
}
在 Program.Main
中我调用 ServiceInjector.Configure()
方法,然后是 ServiceInjector.StartScope()
,然后做一些事情(注入服务),最后(在全局异常处理程序的 finally
块中) 当我希望一切都清理干净时,我会调用 ServiceInjector.KillAllScopes()
。
显然这还不够,因为我的 SampleSingletonService
的 Dispose()
从未被调用(ISampleSingletonService
继承自 IDisposable
)。
这是一个问题,因为我使用 ILoggerProvider(来自另一个 Microsoft.Extensions.
包),它缓存内容并每隔几秒将其刷新到文件中。如果全局异常处理程序发现一些异常,我会记录它并希望将其刷新到磁盘,然后退出应用程序。如果没有调用 Dispose
,应用程序会在最后一条消息被刷新之前关闭,所以我丢失了那条消息。
我显然可以暂停应用程序的时间比刷新所需的时间更长,以确保它完成,但我们只能说这不是完美的解决方案。
我以某种方式尝试过 disposing/cleaning/closing root IServiceProvider
,但它不是从 IDisposable
继承的,我 none 的可用方法似乎正在清理创建的混乱通过它。
我也试过将 _service
设置为 null 并调用 GC.Collect()
,但垃圾收集器似乎没有兴趣帮助我 ;)
出于演示目的,我创建了简约的复制应用程序,它显示了问题(从未记录来自 Dispose
示例服务方法的消息):https://github.com/domints/DotnetDISingletonDisposingPoC
感谢您提供有关如何解决该问题的任何想法。
我找到了解决方案和罪魁祸首。在重新研究这个问题之后,我注意到 svcCollection.BuildServiceProvider()
的文档和代码不是 return IServiceProvider
,而是 ServiceProvider
,它实现了 IServiceProvider
和 IDisposable
.
因此将我的字段更改为:
static ServiceProvider _services;
让我可以访问 Dispose() 方法,我可以优雅地告诉 ServiceProvider 我不再需要他了,这样他就可以自己清理了。
请注意,它在 .NET Core 3.1 和 5.0 中的工作方式相同,无法向其他人保证,但我相信它至少在 2.2 之前是相同的。
您需要在 _services 字段上调用 Dispose。
销毁作用域时,只会销毁范围内的服务,不会销毁单例。
var serviceCollection = new ServiceCollection();
// configure services
using var serviceProvider = serviceCollection.BuildServiceProvider();
using var serviceScope = serviceProvider.CreateScope();
我有这个小型控制台应用程序,我决定为其连接 Microsoft 的 DI,以便它为我处理依赖项。我没有那么多范围,而是瞬态和单例服务。
问题是,它不处理单例服务。也许是因为它不知道我什么时候完成它?可能是其他原因吧。
这是我的静态 class 处理注入的代码(显然在最终应用程序中它有点复杂)
public static class ServiceInjector
{
static IServiceProvider _services;
static Stack<IServiceScope> _scopeStack;
static IServiceProvider _provider => _scopeStack.NullPeek()?.ServiceProvider ?? _services;
public static void Configure()
{
var svcCollection = new ServiceCollection();
svcCollection.AddSingleton<ISampleSingletonService, SampleSingletonService>();
_services = svcCollection.BuildServiceProvider();
_scopeStack = new Stack<IServiceScope>();
}
public static T GetService<T>()
{
return _provider.GetService<T>();
}
public static void StartScope()
{
_scopeStack.Push(_services.CreateScope());
}
public static void KillAllScopes()
{
while(_scopeStack.TryPop(out IServiceScope scope))
{
scope.Dispose();
}
_services = null; // I also tried to convince GC to clean it up and force Dispose. Nope.
}
}
在 Program.Main
中我调用 ServiceInjector.Configure()
方法,然后是 ServiceInjector.StartScope()
,然后做一些事情(注入服务),最后(在全局异常处理程序的 finally
块中) 当我希望一切都清理干净时,我会调用 ServiceInjector.KillAllScopes()
。
显然这还不够,因为我的 SampleSingletonService
的 Dispose()
从未被调用(ISampleSingletonService
继承自 IDisposable
)。
这是一个问题,因为我使用 ILoggerProvider(来自另一个 Microsoft.Extensions.
包),它缓存内容并每隔几秒将其刷新到文件中。如果全局异常处理程序发现一些异常,我会记录它并希望将其刷新到磁盘,然后退出应用程序。如果没有调用 Dispose
,应用程序会在最后一条消息被刷新之前关闭,所以我丢失了那条消息。
我显然可以暂停应用程序的时间比刷新所需的时间更长,以确保它完成,但我们只能说这不是完美的解决方案。
我以某种方式尝试过 disposing/cleaning/closing root IServiceProvider
,但它不是从 IDisposable
继承的,我 none 的可用方法似乎正在清理创建的混乱通过它。
我也试过将 _service
设置为 null 并调用 GC.Collect()
,但垃圾收集器似乎没有兴趣帮助我 ;)
出于演示目的,我创建了简约的复制应用程序,它显示了问题(从未记录来自 Dispose
示例服务方法的消息):https://github.com/domints/DotnetDISingletonDisposingPoC
感谢您提供有关如何解决该问题的任何想法。
我找到了解决方案和罪魁祸首。在重新研究这个问题之后,我注意到 svcCollection.BuildServiceProvider()
的文档和代码不是 return IServiceProvider
,而是 ServiceProvider
,它实现了 IServiceProvider
和 IDisposable
.
因此将我的字段更改为:
static ServiceProvider _services;
让我可以访问 Dispose() 方法,我可以优雅地告诉 ServiceProvider 我不再需要他了,这样他就可以自己清理了。
请注意,它在 .NET Core 3.1 和 5.0 中的工作方式相同,无法向其他人保证,但我相信它至少在 2.2 之前是相同的。
您需要在 _services 字段上调用 Dispose。 销毁作用域时,只会销毁范围内的服务,不会销毁单例。
var serviceCollection = new ServiceCollection();
// configure services
using var serviceProvider = serviceCollection.BuildServiceProvider();
using var serviceScope = serviceProvider.CreateScope();