.NET Core DI 中的异步提供程序
Async provider in .NET Core 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,但是在解析依赖时意义不大,因为:
- Constructors can't be asynchronous,以及
- 构建对象图应该simple, reliable and fast
这意味着所有涉及 I/O 的事情都应该推迟到构建对象图之后。
因此,与其注入连接的 MyClient
,MyClient
应该在第一次 使用 时连接,而不是在创建时连接。
由于您的MyClient
不是应用程序组件而是第三方组件,这意味着您无法确保它“连接[s]时这是第一次使用。"
然而,这应该不是问题,因为 Dependency Inversion Principle 已经告诉我们:
the abstracts are owned by the upper/policy layers
这意味着应用程序组件不应直接依赖于第三方组件,而应依赖于应用程序本身定义的抽象。作为 Composition Root 的一部分,可以编写适配器来实现这些抽象并使应用程序代码适应第三方库。
这样做的一个重要优势是您可以控制应用程序组件使用的 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>
中,这允许客户端仅连接一次,而与调用 GetData
和 SendData
方法的顺序无关, 以及多少次。
这允许您让您的应用程序组件依赖于 IMyAppService
而不是 MyClient
并使用适当的生活方式将 MyClientAdapter
注册为 IMyAppService
。
我只是想知道是否可以在 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,但是在解析依赖时意义不大,因为:
- Constructors can't be asynchronous,以及
- 构建对象图应该simple, reliable and fast
这意味着所有涉及 I/O 的事情都应该推迟到构建对象图之后。
因此,与其注入连接的 MyClient
,MyClient
应该在第一次 使用 时连接,而不是在创建时连接。
由于您的MyClient
不是应用程序组件而是第三方组件,这意味着您无法确保它“连接[s]时这是第一次使用。"
然而,这应该不是问题,因为 Dependency Inversion Principle 已经告诉我们:
the abstracts are owned by the upper/policy layers
这意味着应用程序组件不应直接依赖于第三方组件,而应依赖于应用程序本身定义的抽象。作为 Composition Root 的一部分,可以编写适配器来实现这些抽象并使应用程序代码适应第三方库。
这样做的一个重要优势是您可以控制应用程序组件使用的 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>
中,这允许客户端仅连接一次,而与调用 GetData
和 SendData
方法的顺序无关, 以及多少次。
这允许您让您的应用程序组件依赖于 IMyAppService
而不是 MyClient
并使用适当的生活方式将 MyClientAdapter
注册为 IMyAppService
。