async/await
DI中。
以下を実行すると、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
は、それが作成されたときではなく、初めてusedされたときに接続する必要があります。
[〜#〜]更新[〜#〜]
あなたのMyClient
はアプリケーションコンポーネントではなくサードパーティコンポーネントなので、これは、それが「接続[s]されていることを確認できないことを意味します初めて使用」。
Dependency Inversion Principle はすでに次のことを教えているため、これは問題にはなりません。
アブストラクトは上位層/ポリシー層が所有しています
つまり、アプリケーションコンポーネントはサードパーティコンポーネントに直接依存するのではなく、アプリケーション自体によって定義された抽象化に依存する必要があります。 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
の順序に関係なく、クライアントを1回だけ接続できますメソッドが呼び出され、その回数。
これで、アプリケーションコンポーネントをIMyAppService
ではなくMyClient
に依存させ、MyClientAdapter
をIMyAppService
として適切なライフスタイルに登録できます。