{"id":233,"date":"2020-08-04T07:34:43","date_gmt":"2020-08-04T14:34:43","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/azure-sdk\/?p=233"},"modified":"2022-12-20T09:30:50","modified_gmt":"2022-12-20T17:30:50","slug":"lifetime-management-and-thread-safety-guarantees-of-azure-sdk-net-clients","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/azure-sdk\/lifetime-management-and-thread-safety-guarantees-of-azure-sdk-net-clients\/","title":{"rendered":"Lifetime management for Azure SDK .NET clients"},"content":{"rendered":"<p>When using Azure SDK .NET client libraries in high throughput applications, it&#8217;s important to know how to maximize performance and avoid extra allocations while preventing bugs that could be introduced by accessing data from multiple threads. This article covers the best practices for using clients and models efficiently.<\/p>\n<h2>Client lifetime<\/h2>\n<p>The main rule of Azure SDK client lifetime management is: <strong>treat clients as singletons.<\/strong><\/p>\n<p>There is no need to keep more than one instance of a client for a given set of constructor parameters or client options. This can be implemented in many ways: creating an instance once and passing it around as a parameter, storing an instance in a field, or registering it as a singleton in a dependency injection container of your choice.<\/p>\n<p>\u274c Bad (extra allocations and initialization):<\/p>\n<pre><code>foreach (var secretName in secretNames)\r\n{\r\n    var client = new SecretClient(\r\n        new Uri(\"&lt;secrets_endpoint&gt;\"), \r\n        new DefaultAzureCredential());\r\n    KeyVaultSecret secret = client.GetSecret(secretName);\r\n    \/\/ code omitted for brevity\r\n}\r\n<\/code><\/pre>\n<p>\u2714\ufe0f Good:<\/p>\n<pre><code>var client = new SecretClient(\r\n    new Uri(\"&lt;secrets_endpoint&gt;\"),\r\n    new DefaultAzureCredential());\r\nforeach (var secretName in secretNames)\r\n{\r\n    KeyVaultSecret secret = client.GetSecret(secretName);\r\n    \/\/ code omitted for brevity\r\n}\r\n<\/code><\/pre>\n<p>\u2714\ufe0f Also good:<\/p>\n<pre><code>public class Program\r\n{\r\n    internal static SecretClient Client;\r\n\r\n    public static void Main()\r\n    {\r\n        Client = new SecretClient(\r\n            new Uri(\"&lt;secrets_endpoint&gt;\"), \r\n            new DefaultAzureCredential());\r\n    }\r\n}\r\n\r\npublic class OtherClass\r\n{\r\n    public string DoWork()\r\n    {\r\n        KeyVaultSecret secret = Program.Client.GetSecret(settingName);\r\n        \/\/ code omitted for brevity\r\n    }\r\n}\r\n<\/code><\/pre>\n<h2>Thread-safety: Clients are thread-safe<\/h2>\n<p>We guarantee that all client instance methods are thread-safe and independent of each other (<a href=\"https:\/\/azure.github.io\/azure-sdk\/dotnet_introduction.html#dotnet-service-methods-thread-safety\">guideline<\/a>). This ensures that the recommendation of reusing client instances is always safe, even across threads.<\/p>\n<p>\u2714\ufe0f Good:<\/p>\n<pre><code>var client = new SecretClient(\r\n    new Uri(\"&lt;secrets_endpoint&gt;\"),\r\n    new DefaultAzureCredential());\r\n\r\nforeach (var secretName in secretNames)\r\n{\r\n    \/\/ Using clients from parallel threads\r\n    Task.Run(() =&gt; {\r\n        \/\/ code that uses client here\r\n    });\r\n}\r\n<\/code><\/pre>\n<h2>Thread-safety: Models are not thread-safe<\/h2>\n<p>Because most model use-cases involve a single thread and to avoid incurring an extra synchronization cost the input and output models of the client methods are non-thread-safe and can only be accessed by one thread at a time. The following sample illustrates a bug where accessing a model from multiple threads might cause an undefined behavior.<\/p>\n<p>\u274c Bad:<\/p>\n<pre><code>KeyVaultSecret newSecret = client.SetSecret(\"secret\", \"value\");\r\n\r\nforeach (var tag in tags)\r\n{\r\n    \/\/ Don't use model type from parallel threads\r\n    Task.Run(() =&gt; newSecret.Properties.Tags[tag] = CalculateTagValue(tag));\r\n}\r\n\r\nclient.UpdateSecretProperties(newSecret.Properties);\r\n<\/code><\/pre>\n<p>If you need to access the model from different threads use a synchronization primitive.<\/p>\n<p>\u2714\ufe0f Good:<\/p>\n<pre><code>KeyVaultSecret newSecret = client.SetSecret(\"secret\", \"value\");\r\n\r\nforeach (var tag in tags)\r\n{\r\n    Task.Run(() =&gt;\r\n    {\r\n        lock (newSecret)\r\n        {\r\n            newSecret.Properties.Tags[tag] = CalculateTagValue(tag);\r\n        }\r\n    );\r\n}\r\n\r\nclient.UpdateSecretProperties(newSecret.Properties);\r\n<\/code><\/pre>\n<h2>Clients are immutable<\/h2>\n<p>Clients are immutable after being created, which also makes them safe to share and reuse safely (<a href=\"https:\/\/azure.github.io\/azure-sdk\/general_implementation.html#general-config-behaviour-changes\">guideline<\/a>). This means that after the client is constructed, you cannot change the endpoint it connects to, the credential, and other values passed via the client options.<\/p>\n<p>\u274c Bad (configuration changes are ignored):<\/p>\n<pre><code>var secretClientOptions = new SecretClientOptions()\r\n{\r\n    Retry = \r\n    {\r\n        Delay = TimeSpan.FromSeconds(5)\r\n    }\r\n};\r\n\r\nvar mySecretClient = new SecretClient(\r\n    new Uri(\"&lt;...&gt;\"),\r\n    new DefaultAzureCredential(),\r\n    secretClientOptions);\r\n\r\n\/\/ This has no effect on the mySecretClient instance\r\nsecretClientOptions.Retry.Delay = TimeSpan.FromSeconds(100);\r\n<\/code><\/pre>\n<p><strong>NOTE:<\/strong> An important exception from this rule are credential type implementations that are required to support rolling the key after the client was created (<a href=\"https:\/\/azure.github.io\/azure-sdk\/dotnet_introduction.html#dotnet-auth-rolling-credentials\">guideline<\/a>). Examples of such types include <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/azure.azurekeycredential.update?view=azure-dotnet#Azure_AzureKeyCredential_Update_System_String_\">AzureKeyCredential<\/a> and <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/azure.storage.storagesharedkeycredential.setaccountkey?view=azure-dotnet#Azure_Storage_StorageSharedKeyCredential_SetAccountKey_System_String_\">StorageSharedKeyCredential<\/a>. This feature is to enable long-running applications while using limited-time keys that need to be rolled periodically without requiring application restart or client re-creation.<\/p>\n<h2>Clients are not disposable: Shared HttpClient as default<\/h2>\n<p>One question that comes up often is why aren&#8217;t HTTP-based Azure clients implementing <code>IDisposable<\/code> while internally using an <code>HttpClient<\/code> that is disposable? All Azure SDK clients, by default, use a single shared <code>HttpClient<\/code> instance and don&#8217;t create any other resources that need to be actively freed. The shared client instance persists throughout the entire application lifetime.<\/p>\n<pre><code>\/\/ Both clients reuse the shared HttpClient and don't need to be disposed\r\nvar blobClient = new BlobClient(new Uri(sasUri));\r\nvar blobClient2 = new BlobClient(new Uri(sasUri2));\r\n<\/code><\/pre>\n<h2>Explicitly disposing customer provided HttpClient instances<\/h2>\n<p>If you provide a custom instance of <code>HttpClient<\/code> to an Azure client, you become responsible for managing the <code>HttpClient<\/code> lifetime and disposing it at the right time. We recommend <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.net.http.httpclient?view=netcore-3.1#remarks\">following <code>HttpClient<\/code> best practices<\/a> when customizing the transport.<\/p>\n<pre><code>var httpClient = new HttpClient();\r\n\r\nvar clientOptions = new BlobClientOptions()\r\n{\r\n    Transport = new HttpClientTransport(httpClient)\r\n}\r\n\r\n\/\/ Both client would use the HttpClient instance provided in clientOptions\r\nvar blobClient = new BlobClient(new Uri(sasUri), clientOptions);\r\nvar blobClient2 = new BlobClient(new Uri(sasUri2), clientOptions);\r\n\r\n\/\/...\r\n\r\n\/\/ some time later\r\nhttpClient.Dispose();\r\n<\/code><\/pre>\n<h2>Using ASP.NET Core<\/h2>\n<p>If you are using Azure SDK clients in an ASP.NET Core application, client lifetime management can be simplified with the Microsoft.Extensions.Azure package that provides seamless integration of Azure clients with the ASP.NET Core dependency injection and configuration systems. See the <a href=\"https:\/\/devblogs.microsoft.com\/azure-sdk\/best-practices-for-using-azure-sdk-with-asp-net-core\/\">Best practices for using Azure SDK with ASP.NET Core<\/a> blog post or <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-net\/blob\/master\/sdk\/extensions\/Microsoft.Extensions.Azure\/README.md\">Microsoft.Extensions.Azure package readme<\/a> for details.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When using Azure SDK .NET client libraries in high throughput applications, it&#8217;s important to know how to maximize performance and avoid extra allocations while preventing bugs that could be introduced by accessing data from multiple threads. This article covers the best practices for using clients and models efficiently. Client lifetime The main rule of Azure [&hellip;]<\/p>\n","protected":false},"author":31981,"featured_media":388,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[701,706,703,707,702,705,704],"class_list":["post-233","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-sdk","tag-net","tag-azuresdk","tag-clientlibraries","tag-clientlifetime","tag-clients","tag-sdk","tag-threadsafety"],"acf":[],"blog_post_summary":"<p>When using Azure SDK .NET client libraries in high throughput applications, it&#8217;s important to know how to maximize performance and avoid extra allocations while preventing bugs that could be introduced by accessing data from multiple threads. This article covers the best practices for using clients and models efficiently. Client lifetime The main rule of Azure [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/233","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/users\/31981"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/comments?post=233"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/233\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media\/388"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media?parent=233"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/categories?post=233"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/tags?post=233"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}