Async await syntax gives us the most straightforward way to write asynchronous code.
However, such construction cannot be used in certain scenarios. For instance, using async await keywords is prohibited in constructors.
There are many reasons why it is not possible in the existing language version. Probably, one of the obvious ones is that async methods would return Task
or Task<T>
to be able to properly handle exceptions without crashing the process, and trivially to be able to wait until the async operation is completed.
And if we simply take the following class, build the library containing it, and then look at disassembled code.
public class MyObject
{
public MyObject()
{
//diligently initializing
}
}
We will see the following line.
instance void [System.Runtime]System.Object::.ctor()
where instance
the keyword indicates that the method is an instance method, meaning it is called on an instance of the class, not on the class itself (which would be indicated by the static keyword).
So, technically, it calls some method (constructor) on the already existing object to initialize it.
Could it be made asynchronous hypothetically?
public class MyObject
{
public async MyObject()
{
await InitializeAsync();
}
}
Of course, yes, everything is possible in our world; it only depends on the price and whether it will solve any real problem without introducing new complications and tradeoffs.
I believe that C# language developers have had discussions about it many times, and they clearly understand the feasibility and meaningfulness of incorporating this change.
However, even without language in-built such a feature, we can solve this problem.
There are plenty of approaches that could be used to achieve the asynchronous initialization.
If we are only allowed to have sync operation in constructors, we can intuitively apply the async to sync approach.
public class MyBestObject
{
public MyBestObject()
{
InitializeAsync().GetAwaiter().GetResult();
}
private async Task InitializeAsync()
{
//diligently initializing
await Task.Delay(100);
}
}
This approach is not so bad if there is no synchronization context in place and the async operation is relatively fast, but in general, it is not a recommended practice since it is based on a lot of assumptions about the executing environment and details of the async operation. It leads to inefficient resource consumption, sudden deadlocks in UI applications, and a common violation of the async programming idea of “async all the way.“
A quite standard way for solving this problem is to use factories.
public class MyBestService
{
public async Task InitializeAsync()
{
//diligently initializing
await Task.Delay(100);
}
}
public interface IMyBestServiceFactory
{
Task<MyBestService> CreateAsync(CancellationToken cancellationToken);
}
public sealed class MyBestServiceFactory : IMyBestServiceFactory
{
public MyBestServiceFactory()
{
}
public async Task<MyBestService> CreateAsync(CancellationToken cancellationToken)
{
var service = new MyBestService();
await service.InitializeAsync(cancellationToken);
return service;
}
}
We could either use the static method in MyBestService
class or even specify a dedicated factory for that purpose. The second option is a little bit more compatible with the Dependency Injection pattern since you can request IMyBestServiceFactory
in any class and then just call CreateAsync
the method.
The main drawback of this approach is additional coupling since you (any class uses IMyBestServiceFactory
) need to control the lifetime of a newly created object.
Additionally, it requires adapting solutions that use reflection (Activate.CreateInstace
) or expressions (Expression.New
) to create and initialize instances.
We could do the following trick to avoid problems with the Async Factory pattern.
public class MyBestService
{
private readonly Task _initializeTask;
public MyBestService()
{
_initializeTask = InitializeAsync();
}
public async Task DoSomethingAsync()
{
await _initializeTask;
// Do something async
}
private async Task InitializeAsync()
{
//diligently initializing
await Task.Delay(100);
}
}
As you can see, we are beginning asynchronous initialization in the constructor and saving the reference to the started task. Then, before doing any meaningful operation, we check that _initializeTask
is completed by simply awaiting it.
The using of this approach is very usual both in cases with a self-instantiating way of objects and IoC container.
var myBestService = new MyBestService();
await myBestService.DoSomethingAsync();
However, this approach has several drawbacks:
CancellationToken
.For applications to access Azure services like storage, key vault, or cognitive services, authenticating with Azure is mandatory, regardless of their deployment environment - on Azure, on-premises, or locally during development.
There are several ways it could be achieved, and one of the most secure ones is utilising the approach with the DefaultAzureCredential
class in .NET
That’s what Microsoft says about it
The
DefaultAzureCredential
class provided by the Azure SDK allows apps to use different authentication methods depending on the environment they're run in. This allows apps to be promoted from local development to test environments to production without code changes. You configure the appropriate authentication method for each environment andDefaultAzureCredential
will automatically detect and use that authentication method. The use ofDefaultAzureCredential
should be preferred over manually coding conditional logic or feature flags to use different authentication methods in different environments.
So, in order to securely store PII (Personally Identifiable Information) which can be either Full name, Phone Number, Credit card number, or Social security number, we would like to encrypt it before storing it somewhere and provide viewing original value with only limited access.
One of the possible ways is to use AzureKeyVault API for encryption and decryption.
Although there is no huge impact of creating DefaultAzureCredential
every time, to reduce latency and improve efficiency, it is preferable to have a single instance of it for the whole application.
So, to prepare CryptographyClient
for encryption and decryption API, we need to have the following lines
var tokenCredential = new DefaultAzureCredential();
var keyClient = new KeyClient(new Uri(_configuration.KeyVaultUri), tokenCredential);
KeyVaultKey key = await keyClient.GetKeyAsync(_configuration.KeyName);
_cryptographyClient = new CryptographyClient(key.Id, tokenCredential);
To avoid running them on every request, we can utilise Async initialisation described above
internal sealed class DefaultAzureVaultAdapter : IAzureVaultAdapter
{
private readonly AzureVaultConfiguration _configuration;
private EncryptionAlgorithm _encryptionAlgorithm = EncryptionAlgorithm.RsaOaep256;
private CryptographyClient _cryptographyClient = null!;
private Task _initializationTask = null!;
public DefaultAzureVaultAdapter(AzureVaultConfiguration configuration)
{
_configuration = configuration;
_initializationTask = InitializeAsync();
}
public async Task<string> EncryptAsync(string value, CancellationToken cancellationToken)
{
await _initializationTask;
byte[] inputAsByteArray = Encoding.UTF8.GetBytes(value);
EncryptResult encryptResult = await _cryptographyClient.EncryptAsync(_encryptionAlgorithm, inputAsByteArray, cancellationToken);
return Convert.ToBase64String(encryptResult.Ciphertext);
}
public async Task<string> DecryptAsync(string value, CancellationToken cancellationToken)
{
await _initializationTask;
byte[] inputAsByteArray = Convert.FromBase64String(value);
DecryptResult decryptResult = await _cryptographyClient.DecryptAsync(_encryptionAlgorithm, inputAsByteArray, cancellationToken);
return Encoding.Default.GetString(decryptResult.Plaintext);
}
private async Task InitializeAsync()
{
if (_cryptographyClient is not null)
return;
var tokenCredential = new DefaultAzureCredential();
var keyClient = new KeyClient(new Uri(_configuration.KeyVaultUri), tokenCredential);
KeyVaultKey key = await keyClient.GetKeyAsync(_configuration.KeyName);
_cryptographyClient = new CryptographyClient(key.Id, tokenCredential);
}
}
And finally, register our AzureVaultAdapter
with a singleton in IoC container.
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddAzureVaultAdapter(this IServiceCollection services)
{
services.TryAddSingleton<IAzureVaultAdapter, DefaultAzureVaultAdapter>();
return services;
}
}
In this article, I covered several approaches for asynchronous object initialisation. Understanding requirements is the key to choosing the right approach. You can find the source code used to describe these patterns in this repository.
https://github.com/alex-popov-stenn/CSharpAsyncInitPatterns/tree/main
Thank you for reading! See you next time!