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 客户端对象视为单一实例。 此建议通常通过将 Azure SDK 客户端对象注册为应用的控制反转容器(IoC)容器中的单一实例来实现。 依赖项注入 (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 SDK for .NET 的依赖项注入

或者,可以创建 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));

可以向 Azure SDK 客户端对象提供自定义实例 HttpClient 。 在这种情况下,你负责管理 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 文档中。

另请参阅