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 文档。