在ASP.NET Core中将服务单例注入到演员(Akka.NET)中

3

我正在尝试使用ASP.NET Core内置的DI容器将一个服务的单例注入到一个Actor(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");
}

问题在于演员中的MyService实例与注入到应用程序其余部分的实例不同-即它不是一个单例。
我做错了什么,有更好的方法吗?

你不需要在任何 lambda 函数中运行 system.ActorOf(MyActor.Props(provider.GetService<IMyService>()), "myactor")。它直接在函数内部运行,并且在 Web 应用程序启动之前就已经创建了。 - Neville Nazerane
1个回答

3
那是因为你在ConfigureServices中创建了一个独立的IoC容器。
// Build service provider
var provider = services.BuildServiceProvider();

这行代码将创建一个新的服务提供者(IoC容器)。当你从中解析服务时,它们实际上是单例的(因为它不是从作用域提供者解析的)。

除非使用第三方容器并创建它(例如使用Autofac),否则您不应在ConfigureServices方法内调用.BuildServiceProvider()

无论如何,如果您由于某种原因必须在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 Core使用此容器代替创建自己的容器并将其传递给Configure方法。
虽然这可能解决您的问题,但在ConfigureServices内部执行此类解析并不是很规范化,因此您应该查看文档(或提出单独的问题)以了解如何正确地使用Akka.NET进行DI(很抱歉我不熟悉它,我是Microsoft Orleans的用户:))。
稍微好一点的方式(仍然不完全正确,因为它绕过了DI的思想)是推迟直到调用Configure方法时才实例化Actor。
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中初始化和解析您的服务。

关于单例、范围和Actor的注意事项

请注意,您无法从app.ApplicationServices或服务提供程序中解析作用域服务,否则会抛出异常。当您想要使用默认注册为作用域服务的DbContext时,这可能会成为问题。

您也可以通过覆盖AddDbContext来将其注册为作用域,但请注意"内存泄漏",随着跟踪对象数量的增加,内存消耗也会增加(跟踪的实体数量(>=10k)将显著减少您的跟踪器相关操作)。

还要记住,考虑到DbContext,EF和EF Core不是线程安全的,并且不能被线程访问(或运行多个异步操作,即启动5个查询而不等待然后使用await Task.WaitAll(...))。

尽管Actor保证只能由单个线程在单个时间访问,但如果对它们进行作用域限制,则服务并不是。

这是否有效取决于Akka.NET使用的任务计划程序实现(再次强调,不熟悉其内部结构-即Orleans将持久性抽象为存储提供程序)。


感谢您的详细回答。我完全误判了 BuildServiceProvider()。在我正确理解之前,我将坚持您的第一个建议。 - Dave New
1
添加了一些注释,这可能对您很有帮助,因为单例在ASP.NET Core中可能会变得棘手。 - Tseng

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接