.net核心DI中的异步提供程序 [英] async provider in .net core DI

查看:55
本文介绍了.net核心DI中的异步提供程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我只是想知道在DI期间是否可能有async/await.

执行以下操作,DI无法解析我的服务.

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;
});

解决方案

在对象解析期间无法使用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();
        }
    }
}

适配器从应用程序代码中隐藏连接详细信息.它将MyClient的创建和连接包装在Lazy<T>中,这允许客户端仅连接一次,而与调用GetDataSendData方法的顺序以及调用次数无关.

这使您可以让您的应用程序组件依赖于IMyAppService而不是MyClient,并以适当的生活方式将MyClientAdapter注册为IMyAppService.

I'm just wondering if it's possible to have async/await during DI.

Doing the following, the DI fails to resolve my service.

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

where as the following works perfectly fine.

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

解决方案

It's not possible to use async/await during object resolution, but neither does it make sense when resolving dependencies, because:

This means that everything that involves I/O should be postponed until after the object graph has been constructed.

So instead of injecting a connected MyClient, MyClient should connect when it is used for the first time, not when it is created.

Since your MyClient is not an application component but a third-party component, this means that you can't ensure that it "connect[s] when it is used for the first time."

This shouldn't be a problem, however, because the Dependency Inversion Principle already teaches us that:

the abstracts are owned by the upper/policy layers

This means that application components should not depend on third-party components directly, but instead they should depend on abstractions defined by the application itself. As part of the Composition Root, adapters can be written that implement these abstractions and adapt application code to the third-party libraries.

An important advantage of this is that you are in control over the API that your application components use, which is the key to success here, a it allows the connectivity issues to be hidden behind the abstraction completely.

Here's an example of how your application-tailored abstraction might look like:

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

Do note that this abstraction lacks an ConnectAsync method; this is hidden behind the abstraction. Take a look at the following adapter for instance:

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();
        }
    }
}

The adapter hides the connectivity details from the application code. It wraps the creation and connection of MyClient in a Lazy<T>, which allows the client to be connected just once, independently of in which order the GetData and SendData methods are called, and how many times.

This allows you to let your application components depend on IMyAppService instead of MyClient and register the MyClientAdapter as IMyAppService with the appropriate lifestyle.

这篇关于.net核心DI中的异步提供程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆