Async Lazy In C# – With Great Power Comes Great Responsibility
Tags: dotnet, csharp, coding, programming
Published: 2023-08-18 02:35:00

In the world of software development, performance and efficiency are key. As developers, we constantly strive to write code that not only solves the problem at hand but also does so in the most efficient way possible. One such efficiency technique in C# is the use of the `Lazy<T>` class for lazy initialization. This allows us to defer potentially slow initialization to a point in the execution more just-in-time for when we need to consume a value. But what if we could take this a step further and introduce asynchrony into the mix? Is there something that gives us async lazy functionality?

In [this blog post](https://www.devleader.ca/2023/08/11/async-lazy-in-c-with-great-power-comes-great-responsibility/), we’ll explore the concept of async lazy initialization in C#, using `Lazy<Task<T>>` to achieve this. So, whether you’re a seasoned C# programmer or a curious beginner, buckle up for an exciting journey into the world of async lazy initialization! As always, we’ll touch on the pros and cons of what we’re looking at today.

### Understanding Lazy Initialization

Before we dive into the async world, let’s first understand what lazy initialization is. Lazy initialization is a programming technique where the initialization of an object or value is deferred until it is first accessed. This can be particularly useful when the creation of an object is costly (in terms of time or resources), and the object is not used immediately when the application starts. Without lazy initialization, startup times can grow needlessly out of control. If you’ve been checking out [my content on plugin architectures](https://www.devleader.ca/2023/07/31/plugin-architecture-in-asp-net-core-how-to-master-it/) or [working with Autofac for dependency injection](https://www.devleader.ca/2023/03/31/facade-pattern-a-beginners-how-to-for-simplified-code/), you may have found your initialization code is growing in scope.

In C#, this is achieved using the `Lazy<T>` class. Here’s a simple example:

```csharp
Lazy<MyClass> myObject = new Lazy<MyClass>(() => new());
```

In the above code snippet, `myObject` is a `Lazy<MyClass>` instance. The actual `MyClass` object will not be created until `myObject.Value` is accessed for the first time. This can significantly improve the startup performance of your application if `MyClass` is expensive to create and you access this property only right before you need it the first time. What’s more, the `Lazy<T>` type handles thread safety around the initialization!

You can check out this video for more examples:

{% embed https://youtu.be/qtju5A4JcDs %}

### The Need for Async Lazy Initialization

While `Lazy<T>` is a powerful tool, it doesn’t appear to support asynchronous initialization. This can be a problem when the initialization of the object involves I/O operations or other long-running tasks that would benefit from being run asynchronously. Blocking the main thread for such operations can lead to a poor user experience, as it can make your application unresponsive. Additionally, as async/await patterns continue to become more prevalent in .NET code bases, the desire to have async lazy initialization will continue to grow.

This is where `Lazy<Task<T>>` comes into play. By using `Lazy<Task<T>>`, we can achieve asynchronous lazy initialization. The `Task<T>` represents an asynchronous operation that returns a result. When combined with `Lazy<T>`, it allows the expensive operation to be run asynchronously and its result to be consumed when needed.

So does `Lazy<T>` support async lazy initialization out of the box? Technically, yes, but many of us never consider that we can just replace the type parameter here with a `Task<T>`! By using a task as the type for the lazy wrapper, we essentially get async lazy right out of the box! Many C# developers are already aware of this, but if you were like me, the answer was hiding right under our noses.

### Introducing Lazy&lt;Task&lt;T&gt;&gt;

Let’s see how we can implement asynchronous lazy initialization using `Lazy<Task<T>>`. Here’s a simple example:

```csharp
Lazy<Task<MyClass>> myObject = new Lazy<Task<MyClass>>(() => Task.Run(() => new MyClass()));
```

In the above code snippet, `myObject` is a `Lazy<Task<MyClass>>` instance. The [`Task.Run`](http://Task.Run)`(() => new MyClass())` is an asynchronous operation that creates a new `MyClass` object. This operation will not be run until `myObject.Value` is accessed for the first time. Furthermore, because it’s wrapped in a `Task`, it will be run asynchronously.

And you can do more than just directly instantiate an object! Let’s look at this example:

```csharp
public async Task<MyClass> CreateMyClassAsync()
{
  // simulate being busy!
  await Task.Delay(2000);
  return new MyClass();
}

Lazy<Task<MyClass>> myObject = new Lazy<Task<MyClass>>(CreateMyClassAsync);
```

The code above references an async/await method that aims to demonstrate you can technically have any async code path passed in. The first examples that just show an object’s constructor being called are a little bit contrived because that should ideally be nearly instantaneous. So with this example, hopefully you can start to see the potential with longer running operations.

### Consuming Lazy&lt;Task&lt;T&gt;&gt;

To consume the result of `Lazy<Task<T>>`, we need to await the `Task<T>`:

```csharp
MyClass result = await myObject.Value;
```

In the above code snippet, `myObject.Value` returns a `Task<MyClass>`. By awaiting this task, we can get the `MyClass` object once it’s ready. If the task has not yet been completed, this will asynchronously wait for the task to complete before continuing. This ensures that your application remains responsive, even if the initialization of `MyClass` takes a long time. This is assuming that the rest of your async/await pattern in your code is actually being done properly, of course!

You can watch this video for more details on this:

{% embed https://youtu.be/yObEEa8vftU %}

### The Power of Async Lazy Initialization

Enjoyed this post so far? If you want to see more about [the pros and cons of lazy async along with a nice wrapper, check out the full post](https://www.devleader.ca/2023/08/11/async-lazy-in-c-with-great-power-comes-great-responsibility/)!

For more insights into C# and software development in general, [subscribe to my newsletter](https://www.devleader.ca/newsletter)! You can also check out my [YouTube channel](https://www.youtube.com/@devleader). Stay lazy, and happy coding!