将服务单例注入 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 抽象了存储提供程序背后的持久性)。