将服务单例注入 ASP.NET Core 中的 actor (Akka.NET)
Injecting a service singleton into actor (Akka.NET) in ASP.NET Core
我正在尝试使用 ASP.NET Core 的内置 DI 容器将服务的 单例 注入到参与者 (Akka.NET) 中。
我在 ConfigureServices
中完成了以下操作:
public void ConfigureServices(IServiceCollection services)
{
// ..
// Register singleton of service
services.AddSingleton<IMyService, MyService>();
// Build service provider
var provider = services.BuildServiceProvider();
// Create actor system
var system = ActorSystem.Create("MyActorSystem");
// Inject service singleton into actor
directory.MyActorRef
= system.ActorOf(MyActor.Props(provider.GetService<IMyService>()), "myactor");
}
问题是 Actor 中的 MyService 实例不同于注入到应用程序其余部分的实例 - 即它不是单例。
我做错了什么,有更好的方法吗?
那是因为您在 ConfigureServices
中创建了一个单独的 IoC 容器
// Build service provider
var provider = services.BuildServiceProvider();
这一行将创建一个新的服务提供者(IoC 容器)。当您从它解析服务时,它们实际上是单例(因为它不是从作用域提供者解析的)。
你不应该在你的 ConfigureServices
方法中调用 .BuildServiceProvider()
,除非使用第 3 方容器并创建它(即使用 Autofac 时)。
无论如何,如果您出于某种原因必须在 ConfigureServices
中创建提供者,您需要将 ConfigureServices
的签名更改为
// Return value from void to IServiceProvider
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var provider = services.BuildServiceProvider();
// don't call services.AddXxx(..) after this point! The container is already created and its registrations can't be changed
...
return provider;
}
这将使 ASP.NET 核心使用此容器,而不是创建自己的容器并将其传递给 Configure
方法。
虽然这可能会解决您眼前的问题,但在 ConfigureServices
内部进行这种解析并不是很干净,您应该使用有关如何正确使用 DI 的文档(或提出一个单独的问题)Akka.NET(抱歉不熟悉,我是 Microsoft Orleans 用户 :))。
稍微好一点(仍然不完全正确,因为它围绕 DI 的想法工作)的方法是延迟 actor 的实例化,直到调用 Configure
方法。
public void ConfigureServices(IServiceCollection services)
{
// ..
// Register singleton of service
services.AddSingleton<IMyService, MyService>();
}
public void Configure(IApplicationBuilder app)
{
// Create actor system
var system = ActorSystem.Create("MyActorSystem");
// Inject service singleton into actor
directory.MyActorRef
= system.ActorOf(MyActor.Props(app.ApplicationServices.GetService<IMyService>()), "myactor");
}
或
public void ConfigureServices(IServiceCollection services)
{
// ..
// Register singleton of service
services.AddSingleton<IMyService, MyService>();
}
// inject it in Configure
public void Configure(IApplicationBuilder app, IMyService myService)
{
// Create actor system
var system = ActorSystem.Create("MyActorSystem");
// Inject service singleton into actor
directory.MyActorRef
= system.ActorOf(MyActor.Props(myService), "myactor");
}
这将初始化并解析您在 Configure
中的服务。
关于单例、范围和参与者的备注
P.S。请记住,您 无法解析来自 app.ApplicationServices
或服务提供商的作用域服务 ,它会抛出异常。当您想使用默认注册为范围服务的 DbContext 时,这可能会成为一个问题。
您也可以通过覆盖 AddDbContext
将其注册为作用域,但请注意 "memory leaks",随着跟踪对象数量的增加,内存消耗(以及大量跟踪的实体(>=10k)将显着减少您的跟踪器相关操作)。
考虑到 DbContext,还要记住 EF 和 EF Core 不是线程安全的,并且不能被线程访问(或 运行 多个异步操作,即开始 5 个查询 w/o 等待然后使用 await Task.WaitAll(...)
).
虽然可以保证 actor 一次只能由一个线程访问,但如果您确定服务范围,则不会。
它的效果如何取决于 Akka.NET 使用的 Task Scheduler 实现(同样,不熟悉它的内部结构 - 即 Orleans 抽象了存储提供程序背后的持久性)。
我正在尝试使用 ASP.NET Core 的内置 DI 容器将服务的 单例 注入到参与者 (Akka.NET) 中。
我在 ConfigureServices
中完成了以下操作:
public void ConfigureServices(IServiceCollection services)
{
// ..
// Register singleton of service
services.AddSingleton<IMyService, MyService>();
// Build service provider
var provider = services.BuildServiceProvider();
// Create actor system
var system = ActorSystem.Create("MyActorSystem");
// Inject service singleton into actor
directory.MyActorRef
= system.ActorOf(MyActor.Props(provider.GetService<IMyService>()), "myactor");
}
问题是 Actor 中的 MyService 实例不同于注入到应用程序其余部分的实例 - 即它不是单例。
我做错了什么,有更好的方法吗?
那是因为您在 ConfigureServices
// Build service provider
var provider = services.BuildServiceProvider();
这一行将创建一个新的服务提供者(IoC 容器)。当您从它解析服务时,它们实际上是单例(因为它不是从作用域提供者解析的)。
你不应该在你的 ConfigureServices
方法中调用 .BuildServiceProvider()
,除非使用第 3 方容器并创建它(即使用 Autofac 时)。
无论如何,如果您出于某种原因必须在 ConfigureServices
中创建提供者,您需要将 ConfigureServices
的签名更改为
// Return value from void to IServiceProvider
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var provider = services.BuildServiceProvider();
// don't call services.AddXxx(..) after this point! The container is already created and its registrations can't be changed
...
return provider;
}
这将使 ASP.NET 核心使用此容器,而不是创建自己的容器并将其传递给 Configure
方法。
虽然这可能会解决您眼前的问题,但在 ConfigureServices
内部进行这种解析并不是很干净,您应该使用有关如何正确使用 DI 的文档(或提出一个单独的问题)Akka.NET(抱歉不熟悉,我是 Microsoft Orleans 用户 :))。
稍微好一点(仍然不完全正确,因为它围绕 DI 的想法工作)的方法是延迟 actor 的实例化,直到调用 Configure
方法。
public void ConfigureServices(IServiceCollection services)
{
// ..
// Register singleton of service
services.AddSingleton<IMyService, MyService>();
}
public void Configure(IApplicationBuilder app)
{
// Create actor system
var system = ActorSystem.Create("MyActorSystem");
// Inject service singleton into actor
directory.MyActorRef
= system.ActorOf(MyActor.Props(app.ApplicationServices.GetService<IMyService>()), "myactor");
}
或
public void ConfigureServices(IServiceCollection services)
{
// ..
// Register singleton of service
services.AddSingleton<IMyService, MyService>();
}
// inject it in Configure
public void Configure(IApplicationBuilder app, IMyService myService)
{
// Create actor system
var system = ActorSystem.Create("MyActorSystem");
// Inject service singleton into actor
directory.MyActorRef
= system.ActorOf(MyActor.Props(myService), "myactor");
}
这将初始化并解析您在 Configure
中的服务。
关于单例、范围和参与者的备注
P.S。请记住,您 无法解析来自 app.ApplicationServices
或服务提供商的作用域服务 ,它会抛出异常。当您想使用默认注册为范围服务的 DbContext 时,这可能会成为一个问题。
您也可以通过覆盖 AddDbContext
将其注册为作用域,但请注意 "memory leaks",随着跟踪对象数量的增加,内存消耗(以及大量跟踪的实体(>=10k)将显着减少您的跟踪器相关操作)。
考虑到 DbContext,还要记住 EF 和 EF Core 不是线程安全的,并且不能被线程访问(或 运行 多个异步操作,即开始 5 个查询 w/o 等待然后使用 await Task.WaitAll(...)
).
虽然可以保证 actor 一次只能由一个线程访问,但如果您确定服务范围,则不会。
它的效果如何取决于 Akka.NET 使用的 Task Scheduler 实现(同样,不熟悉它的内部结构 - 即 Orleans 抽象了存储提供程序背后的持久性)。