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 客户端使用的模型对象(无论是输入模型还是输出模型)在默认情况下都不是线程安全的。 涉及模型对象的大多数用例仅使用单个线程。 因此,对于这些对象而言,实现同步作为默认行为的成本太高。 下面的代码演示了从多个线程访问模型可能导致未定义的行为的 bug:

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 的详细信息,请参阅用于 .NET 的 Azure SDK 的依赖关系注入

或者,你可以创建一个 SDK 客户端实例,并将其提供给需要客户端的方法。 这是为了避免不必要地实例化具有相同参数的相同 SDK 客户端对象。 此操作是不必要的,会造成浪费。

客户端不可释放

通常会出现两个最终问题:

  • 使用完 Azure SDK 客户端对象后,是否需要释放它?
  • 为什么基于 HTTP 的 Azure SDK 客户端对象无法释放?

在内部,所有 Azure SDK 客户端均使用单个共享 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 文档。

另请参阅