.NET Core DI中的异步提供程序

42

我在想在依赖注入期间是否可能使用async/await

尝试这样做时,依赖注入无法解析我的服务。

services.AddScoped(async provider => 
{
  var client = new MyClient();
  await client.ConnectAsync();
  return client;
});

然而,以下内容完全正常运行。

services.AddScoped(provider => 
{
  var client = new MyClient();
  client.ConnectAsync().Wait();
  return client;
});
1个回答

67

虽然在对象解析期间理论上可以使用async/await,但您应考虑以下限制:

由于这些限制,最好将涉及I/O的所有操作推迟到对象图构建之后。

因此,不要注入已连接的MyClient,而是在第一次使用时连接MyClient——而不是在创建时连接。

由于您的MyClient不是一个应用程序组件,而是一个第三方组件,这意味着您无法确保它“在第一次使用时连接”。

这不应该是一个问题,因为依赖倒置原则已经教给我们:

抽象层由上层/策略层拥有

这意味着应用程序组件不应直接依赖于第三方组件,而应该依赖于应用程序本身定义的抽象。作为组合根的一部分,可以编写适配器来实现这些抽象,并将应用程序代码适应到第三方库。

这样做的一个重要优点是,您可以控制应用程序组件使用的API,这是成功的关键,因为它允许完全隐藏连接问题在抽象后面。

以下是您的应用程序定制抽象的示例:

public interface IMyAppService
{
    Task<Data> GetData();
    Task SendData(Data data);
}

请注意,此抽象缺少ConnectAsync方法;这是在抽象后面隐藏的。例如,请查看以下适配器:
public sealed class MyClientAdapter : IMyAppService, IDisposable
{
    private readonly Lazy<Task<MyClient>> connectedClient;

    public MyClientAdapter()
    {
        this.connectedClient = new Lazy<Task<MyClient>>(async () =>
        {
            var client = new MyClient();
            await client.ConnectAsync();
            return client;
        });
    }

    public async Task<Data> GetData()
    {
        var client = await this.connectedClient.Value;
        return await client.GetData();
    }

    public async Task SendData(Data data)
    {
        var client = await this.connectedClient.Value;
        await client.SendData(data);
    }

    public void Dispose()
    {
        if (this.connectedClient.IsValueCreated)
            this.connectedClient.Value.Dispose();
    }
}

适配器将连接细节从应用程序代码中隐藏。它在Lazy<T>中包装了MyClient的创建和连接,这使得客户端只需连接一次,而不受调用GetDataSendData方法的顺序以及次数的影响。
这使您可以让应用程序组件依赖于IMyAppService而不是MyClient,并将MyClientAdapter注册为具有适当生命周期的IMyAppService

嗯!关于这个问题。我担心在后续过程中可能会出现问题,因为我无法保证第一种方法能够成功调用“MyClient”。 - rethabile
如果你在第一次使用时连接成功,那么在执行第二个方法时就不应该失败。 - Steven
2
嗨@DougLampe,我不同意这个观点。_实现_可能需要实现IDisposable,但_抽象_肯定不需要,因为使用组件根本不应该能够调用Dispose。由于只有DI容器应该处理此适配器的处理,因此在实现中才需要IDisposable。通常,在抽象上实现IDisposable会导致泄漏的抽象。 - Steven
2
这是一个非常酷的模式。但是,如果我想在应用程序启动时而不是即时运行 ConnectAsync(),会发生什么情况?(我试图避免在初始连接上进行低延迟操作时出现超时和延迟) - gabe
2
@cah1r:我反对在IMyAppService这个抽象类上打上IDisposable接口的标记。像MyClientAdapter这样的实现类实现IDisposable是完全可以的。 - Steven
显示剩余17条评论

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