Dependency injection 避免需要异步初始化的类型的所有DI反模式

Dependency injection 避免需要异步初始化的类型的所有DI反模式,dependency-injection,initialization,async-await,simple-injector,abstract-factory,Dependency Injection,Initialization,Async Await,Simple Injector,Abstract Factory,我有一个类型连接,需要异步初始化。此类型的实例由其他几种类型(例如,存储)使用,每种类型也需要异步初始化(静态,而不是每个实例,并且这些初始化还取决于连接)。最后,我的逻辑类型(例如,logic)使用这些存储实例。目前使用的是简单的喷油器 我尝试了几种不同的解决方案,但总有一种反模式存在 显式初始化(时间耦合) 我当前使用的解决方案具有时间耦合反模式: public sealed class Connections { Task InitializeAsync(); } public s

我有一个类型
连接
,需要异步初始化。此类型的实例由其他几种类型(例如,
存储
)使用,每种类型也需要异步初始化(静态,而不是每个实例,并且这些初始化还取决于
连接
)。最后,我的逻辑类型(例如,
logic
)使用这些存储实例。目前使用的是简单的喷油器

我尝试了几种不同的解决方案,但总有一种反模式存在


显式初始化(时间耦合) 我当前使用的解决方案具有时间耦合反模式:

public sealed class Connections
{
  Task InitializeAsync();
}

public sealed class Storage : IStorage
{
  public Storage(Connections connections);
  public static Task InitializeAsync(Connections connections);
}

public sealed class Logic
{
  public Logic(IStorage storage);
}

public static class GlobalConfig
{
  public static async Task EnsureInitialized()
  {
    var connections = Container.GetInstance<Connections>();
    await connections.InitializeAsync();
    await Storage.InitializeAsync(connections);
  }
}
这里的问题是我们回到了时间耦合,这一次分散在整个系统中。此外,这种方法要求所有公共成员都是异步方法


因此,这里有两种DI设计观点存在分歧:

  • 消费者希望能够注入准备好使用的实例
  • DI容器用力推动
问题是——特别是异步初始化——如果DI容器对“简单构造函数”方法采取强硬态度,那么它们只是强迫用户在其他地方进行自己的初始化,这带来了自己的反模式。例如:“不,这种特性对于简单注入器或任何其他DI容器都没有意义,因为它在依赖项注入方面违反了一些重要的基本规则。”然而,严格地“按基本规则”玩显然会迫使其他反模式看起来更糟

问题是:是否有一种异步初始化解决方案可以避免所有反模式


更新:AzureConnections的完整签名(上面称为
连接
):


虽然我很确定以下内容不是你想要的,但你能解释一下为什么它不能解决你的问题吗

public sealed class AzureConnections
{
    private readonly Task<CloudStorageAccount> storage;

    public AzureConnections()
    {
        this.storage = Task.Factory.StartNew(InitializeStorageAccount);
        // Repeat for other cloud 
    }

    private static CloudStorageAccount InitializeStorageAccount()
    {
        // Do any required initialization here...
        return new CloudStorageAccount( /* Constructor arguments... */ );
    }

    public CloudStorageAccount CloudStorageAccount
    {
        get { return this.storage.Result; }
    }
}
公共密封类AzureConnections
{
专用只读任务存储;
公共AzureConnections()
{
this.storage=Task.Factory.StartNew(InitializeStorageAccount);
//对其他云重复此操作
}
私有静态CloudStorageAccount初始值estorageAccount()
{
//在此处执行任何必需的初始化。。。
返回新的CloudStorageAccount(/*构造函数参数…*/);
}
公共CloudStorageAccount CloudStorageAccount
{
获取{返回this.storage.Result;}
}
}
为了保持设计清晰,我只实现了一个云属性,但是其他两个可以用类似的方式实现

AzureConnections
构造函数不会阻塞,即使初始化各种云对象需要很长时间

另一方面,它将开始工作,并且由于.NET任务的行为类似于承诺,因此当您第一次尝试访问该值时(使用
结果
),它将返回由
InitializeStrageAccount
生成的值

我得到的强烈印象是,这不是你想要的,但由于我不明白你想解决什么问题,我想我会留下这个答案,这样至少我们会有一些事情要讨论。

你的问题,而且,是一个典型的问题。这是典型的,有两个原因:

  • 您需要(或者更确切地说需要)异步启动初始化,以及
  • 您的应用程序框架(azure函数)支持异步启动初始化(或者更确切地说,它周围似乎没有什么框架)。 这使您的情况与典型场景有所不同,这可能会使讨论常见模式变得更加困难
  • 但是,即使在您的情况下,解决方案也相当简单和优雅:

    从保存初始化的类中提取初始化,并将其移动到。此时,您可以在容器中注册这些类之前创建和初始化这些类,并将这些初始化的类作为注册的一部分馈送到容器中。

    这在您的特定情况下运行良好,因为您希望执行一些(一次性)启动初始化。启动初始化通常在配置容器之前完成(如果需要完全组合的对象图,有时在配置之后)。在我看到的大多数情况下,初始化可以在之前完成,在您的情况下也可以有效地完成

    正如我所说的,与正常情况相比,你的情况有点奇怪。标准是:

    • 启动初始化是同步的。框架(如ASP.NET Core)通常不支持启动阶段的异步初始化
    • 初始化通常需要根据请求及时完成,而不是根据应用程序提前完成。通常,需要初始化的组件的生命周期很短,这意味着我们通常在第一次使用时初始化这样的实例(换句话说:及时)
    异步启动初始化通常没有真正的好处。这并没有实际的性能优势,因为在启动时,无论如何只会有一个线程在运行(尽管我们可以将其并行化,但这显然不需要异步)。还请注意,尽管某些应用程序类型在通过异步执行同步时可能会死锁,但在合成根目录中,我们确切地知道我们使用的是哪种应用程序类型,以及这是否会成为问题。组合根始终是特定于应用程序的。换句话说,当我们在非死锁应用程序(例如ASP.NET核心、Azure函数等)的组合根中进行初始化时,异步进行启动初始化通常没有任何好处

    因为在合成根目录中,我们知道通过异步同步是否是一个问题,我们甚至可以决定在第一次使用时同步地进行初始化。因为我
    public sealed class Connections
    {
      private Task InitializeAsync(); // Use Lazy internally
    
      // Used to be a property BobConnection
      public X GetBobConnectionAsync()
      {
        await InitializeAsync();
        return BobConnection;
      }
    }
    
    public sealed class Storage : IStorage
    {
      public Storage(Connections connections);
      private static Task InitializeAsync(Connections connections); // Use Lazy internally
      public async Task<Y> IStorage.GetAsync()
      {
        await InitializeAsync(_connections);
        var connection = await _connections.GetBobConnectionAsync();
        return await connection.GetYAsync();
      }
    }
    
    public sealed class Logic
    {
      public Logic(IStorage storage);
      public async Task<Y> GetAsync()
      {
        return await _storage.GetAsync();
      }
    }
    
    public sealed class AzureConnections
    {
      public AzureConnections();
    
      public CloudStorageAccount CloudStorageAccount { get; }
      public CloudBlobClient CloudBlobClient { get; }
      public CloudTableClient CloudTableClient { get; }
    
      public async Task InitializeAsync();
    }
    
    public sealed class AzureConnections
    {
        private readonly Task<CloudStorageAccount> storage;
    
        public AzureConnections()
        {
            this.storage = Task.Factory.StartNew(InitializeStorageAccount);
            // Repeat for other cloud 
        }
    
        private static CloudStorageAccount InitializeStorageAccount()
        {
            // Do any required initialization here...
            return new CloudStorageAccount( /* Constructor arguments... */ );
        }
    
        public CloudStorageAccount CloudStorageAccount
        {
            get { return this.storage.Result; }
        }
    }
    
                    services.AddSingleton<IWebProxy>((sp) => 
                    {
                        //Notice the GetService outside the Task.  It was locking when it was inside
                        var data = sp.GetService<IData>();
    
                        return Task.Run(async () =>
                        {
                            try
                            {
                                var credentials = await data.GetProxyCredentialsAsync();
                                if (credentials != null)
                                {
                                    return new WebHookProxy(credentials);
                                }
                                else
                                {
                                    return (IWebProxy)null;
                                }
                            }
                            catch(Exception ex)
                            {
                                throw;
                            }
                        }).Result;  //Back to sync
                    });