【问题标题】:Async Initialisation for Transient ASP.NET core瞬态 ASP.NET 核心的异步初始化
【发布时间】:2020-10-12 05:08:41
【问题描述】:

我有一个临时服务必须进行异步初始化(连接到不同的服务器)。 它公开了一个属性public Task InitTask,可以等到服务初始化。 它还公开了在InitTask 完成后可以保存访问的子服务。

public class Service
{
    public Task InitTask { get; }

    public ISubService1 SubService1 { get; }
    public ISubService2 SubService2 { get; }
    public ISubService3 SubService3 { get; }
}

与其他服务器的连接提供的所有功能都由这些子服务捕获。 通常我会注入主服务,然后等待它完成初始化,然后使用这些子服务之一。 但我只想注入这些子服务。

一开始我试过

services.AddTransient<Service>()
    .AddTransient(provider =>
    {
        var server = provider.GetService<Service>();
        return server.SubService1;
    })
    .AddTransient(provider =>
    {
        var server = provider.GetService<Service>();
        return server.SubService2;
    })
    .AddTransient(provider =>
    {
        var server = provider.GetService<Service>();
        return server.SubService3;
    });

但这有一个明显的问题是没有等待主服务的InitTask

然后(因为子服务是通过接口公开的)我尝试为子服务编写包装类,例如

public class WrapperSubService1 : ISubService1
{
    private readonly Service server;

    public WrapperSubService1(Service server)
    {
        this.server = server;
    }

    private async ValueTask<ISubService1> GetSubService1Async()
    {
        await server.InitTask
        return server.SubService1;
    }

    // interface implementations

    public async Task<Example> GetExampleAsync(...) 
    {
        var subService1 = await this.GetSubService1Async();

        return await subService1.GetExampleAsync(...);
    }

    // many more (also some events and properties)
}

并在启动时执行

services.AddTransient<Service>()
    .AddTransient<ISubService1, WrapperSubService1>()
    .AddTransient<ISubService2, WrapperSubService2>()
    .AddTransient<ISubService3, WrapperSubService3>();

但这也有一个明显的缺陷:代码重复。

我希望是这样的:

services.AddTransient<Service>()
    .AddTransient(async provider =>
    {
        var server = provider.GetService<Service>();
        await server.InitTask;
        return server.SubService1;
    })
    .AddTransient(async provider =>
    {
        var server = provider.GetService<Service>();
        await server.InitTask;
        return server.SubService2;
    })
    .AddTransient(async provider =>
    {
        var server = provider.GetService<Service>();
        await server.InitTask;
        return server.SubService3;
    });

但这只会暴露Task&lt;SubService1&gt; 以供注入。

有什么想法吗?

【问题讨论】:

标签: c# asp.net-core dependency-injection async-await


【解决方案1】:

我认为在这种情况下您实际上可以使用显式接口实现:

Service 类更改为

public class Service : ISubService1, ISubService2, ISubService3
{
    public Task InitTask { get; }

    public ISubService1 SubService1 { get; }
    public ISubService2 SubService2 { get; }
    public ISubService3 SubService3 { get; }

....


    private async ValueTask<ISubService1> GetSubService1Async()
    {
        await server.InitTask
        return server.SubService1;
    }

    // interface implementations

    async Task<Example> ISubService1.GetExampleAsync(...) 
    {
        var subService1 = await this.GetSubService1Async();

        return await subService1.GetExampleAsync(...);
    }

....

    async Task<Example> ISubService2.GetExampleAsync(...) 
    {
        var subService1 = await this.GetSubService2Async();

        return await subService1.GetExampleAsync(...);
    }
// and so on
}

【讨论】:

    【解决方案2】:

    对于这种情况,我找到的唯一解决方案是:

    1. 使您的初始化同步。特别是在 ASP.NET 上,我认为这是一个不错的选择,因为在初始化完成之前不接收请求是有意义的。
    2. 拥有一个单独的“模式部署程序”项目,在部署您的服务之前进行这种初始化(创建队列/表)。这是我倾向于在现代应用中使用的解决方案。

    【讨论】:

      【解决方案3】:

      我确实认为暴露Task&lt;SubService1&gt; 很好。

      至于解析服务的代码是这样的:

      var subService1 = await serviceProvider.GetService<Task<SubService1>>();
      var result = await subService1.GetExampleAsync();
      

      看起来很优雅。或者您可以编写一个扩展方法,例如:

      public static Task<T> GetServiceAsync<T>(this IServiceProvider provider) => provider.GetService<Task<T>>();
      

      然后

      var subService1 = await serviceProvider.GetServiceAsync<SubService1>();
      

      会更清楚。

      另外,基于Microsoft.Extensions.DependencyInjection的默认DI的设计不像对象容器那样工作,而只是一个IoC接口(这与众所周知的DI容器如autofac不同)。我们不应该直接从提供者那里检索每个对象,即使它看起来更优雅。

      因此,建议直接从主服务subService1 = provider.GetService&lt;MainService&gt;().GetService1Async(); 获取子服务。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-01-29
        • 1970-01-01
        • 2017-01-26
        • 1970-01-01
        • 2019-12-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多