Azure SDK オブジェクトのスレッド セーフとクライアントの有効期間の管理

この記事は、Azure SDK を使用するときのスレッド セーフの問題を理解するのに役立ちます。 また、SDK の設計がクライアントの有効期間の管理にどのように影響するかについても説明します。 Azure SDK クライアント オブジェクトを破棄する必要がない理由について説明します。

スレッド セーフ

すべての Azure SDK クライアント オブジェクトはスレッドセーフであり、互いに独立しています。 この設計により、クライアント インスタンスの再利用が、たとえスレッド間であっても常に安全であることが保証されます。 たとえば、次のコードでは複数のタスクが起動されますが、スレッド セーフです。

var client = new SecretClient(
    new Uri("<secrets_endpoint>"), new DefaultAzureCredential());

foreach (var secretName in secretNames)
{
    // Using clients from parallel threads
    Task.Run(() => Console.WriteLine(client.GetSecret(secretName).Value));
}

SDK クライアントによって使用されるモデル オブジェクトは、入力モデルか出力モデルかにかかわらず、既定ではスレッドセーフではありません。 モデル オブジェクトを含むほとんどのユース ケースでは、使用されるスレッドは 1 つのみです。 そのため、既定の動作として同期を実装するコストは、これらのオブジェクトに対して高すぎます。 次のコードは、複数のスレッドからモデルにアクセスすると、未定義の動作が発生する可能性があるバグを示したものです。

KeyVaultSecret newSecret = client.SetSecret("secret", "value");

foreach (var tag in tags)
{
    // Don't use model type from parallel threads
    Task.Run(() => newSecret.Properties.Tags[tag] = CalculateTagValue(tag));
}

client.UpdateSecretProperties(newSecret.Properties);

異なるスレッドからモデルにアクセスするには、同期コードを独自に実装する必要があります。 次に例を示します。

KeyVaultSecret newSecret = client.SetSecret("secret", "value");

// Code omitted for brevity

foreach (var tag in tags)
{
    Task.Run(() =>
    {
        lock (newSecret)
        {
            newSecret.Properties.Tags[tag] = CalculateTagValue(tag);
        }
    );
}

client.UpdateSecretProperties(newSecret.Properties);

クライアントの有効期間

Azure SDK クライアントはスレッドセーフであるため、特定のコンストラクター パラメーターのセットに対して複数の SDK クライアント オブジェクトを構築する必要はありません。 構築された Azure SDK クライアント オブジェクトは、シングルトンとして扱います。 この推奨事項は、通常、アプリの制御の反転 (IoC) コンテナーでシングルトンとして Azure SDK クライアント オブジェクトを登録することによって実装されます。 依存関係の挿入 (DI) は、SDK クライアント オブジェクトへの参照を取得するために使用されます。 次の例では、シングルトン クライアント オブジェクトの登録を示します。

var builder = Host.CreateApplicationBuilder(args);

var endpoint = builder.Configuration["SecretsEndpoint"];
var blobServiceClient = new BlobServiceClient(
    new Uri(endpoint), new DefaultAzureCredential());

builder.Services.AddSingleton(blobServiceClient);

Azure SDK での DI の実装に関する詳細については、「Azure .NET SDK での依存関係の挿入」を参照してください。

または、SDK クライアント インスタンスを作成し、クライアントを必要とするメソッドにそれを提供することもできます。 ポイントとなるのは、同じパラメーターを使用する同じ SDK クライアント オブジェクトの不要なインスタンス化を回避することです。 それは不要なだけでなく無駄でもあります。

クライアントは破棄できない

最後に、次の 2 つのことがよく疑問になります。

  • 使い終わった Azure SDK クライアント オブジェクトは、破棄する必要があるか?
  • HTTP ベースの Azure SDK クライアント オブジェクトを破棄できないのはなぜか?

内部的には、すべての Azure SDK クライアントによって 1 つの共有 HttpClient インスタンスが使用されています。 クライアントで、アクティブに解放する必要がある他のリソースを作成することはありません。 共有 HttpClient インスタンスは、アプリケーションの有効期間全体を通して維持されます。

// Both clients reuse the shared HttpClient and don't need to be disposed
var blobClient = new BlobClient(new Uri(sasUri));
var blobClient2 = new BlobClient(new Uri(sasUri2));

HttpClient のカスタム インスタンスを、Azure SDK クライアント オブジェクトに提供することができます。 この場合は、開発者が、HttpClient の有効期間を管理し、適切なタイミングで適切に破棄する必要があります。

var httpClient = new HttpClient();

var clientOptions = new BlobClientOptions()
{
    Transport = new HttpClientTransport(httpClient)
};

// Both clients would use the HttpClient instance provided in clientOptions
var blobClient = new BlobClient(new Uri(sasUri), clientOptions);
var blobClient2 = new BlobClient(new Uri(sasUri2), clientOptions);

// Code omitted for brevity

// You're responsible for properly disposing httpClient some time later
httpClient.Dispose();

HttpClient インスタンスの適切な管理と破棄に関する詳細なガイダンスについては、HttpClient のドキュメントを参照してください。

関連項目