【问题标题】:Injecting a service singleton into actor (Akka.NET) in ASP.NET Core在 ASP.NET Core 中将服务单例注入到 Actor (Akka.NET) 中
【发布时间】:2018-04-19 11:14:20
【问题描述】:

我正在尝试使用 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 实例与注入到应用程序其余部分的实例不同 - 即它不是单例。

我做错了什么,有更好的方法吗?

【问题讨论】:

  • 你没有在任何 lamda 中运行 system.ActorOf(MyActor.Props(provider.GetService&lt;IMyService&gt;()), "myactor")。这直接在函数内部运行,并在 Web 应用程序启动之前创建

标签: asp.net-core asp.net-core-2.0 akka.net


【解决方案1】:

那是因为您在 ConfigureServices 中创建了一个单独的 IoC 容器

// Build service provider
var provider = services.BuildServiceProvider();

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

你永远不应该在你的ConfigureServices 方法中调用.BuildServiceProvider(),除非在使用 3rd 方容器并创建它时(即使用 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 Core 使用此容器,而不是创建自己的容器并将其传递给 Configure 方法。

虽然这可能会立即解决您的问题,但在 ConfigureServices 内部进行这种解析并不是很干净,您应该使用文档(或提出单独的问题)了解如何正确使用 DI 与 Akka.NET(对不起不熟悉,我是 Microsoft Orleans 用户 :))。

一个稍微好一点(仍然不完全正确,因为它围绕 DI 的想法工作)的方法是延迟演员的实例化,直到调用 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 中的服务。

关于单例、作用域和参与者的备注

附:请记住,您无法从app.ApplicationServices 或服务提供商解析范围服务,它会抛出异常。当您想要使用默认注册为作用域服务的 DbContext 时,这可能会成为一个问题。

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

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

虽然一个演员被保证一次只能被一个线程访问,但如果你确定它们的范围,服务就不会。

这工作的好坏取决于 Akka.NET 使用的任务调度器实现(同样,不熟悉它的内部 - 即 Orleans 抽象存储提供程序背后的持久性)。

【讨论】:

  • 感谢您的详尽回答。我完全误判了BuildServiceProvider()。我会坚持你的第一个建议,直到我正确地做到这一点
  • 添加了一些您可能会感兴趣的注释,因为单例在 ASP.NET Core 中可能会变得很棘手
猜你喜欢
  • 2019-10-22
  • 1970-01-01
  • 2016-11-05
  • 1970-01-01
  • 2016-07-07
  • 2018-08-07
  • 1970-01-01
  • 1970-01-01
  • 2020-09-15
相关资源
最近更新 更多