async-awaitdependency-injectionconstructorc#-8.0

How to do self-contained Lazy Initialization using async/await


I am trying to initialize a class asynchronously using dependency injection. I have seen this article https://blog.stephencleary.com/2013/01/async-oop-2-constructors.html but I am trying to initialize the class on the first call of a method.

I have a class like the one below, but I am using how to start the task in the DoWork method (note I am very new to await/async).

public class TestClass: ITestClass
{
    private readonly ILogger<TestClass> logger;
    private readonly Task initializeTask;
    private IDependencyClass depenencyClass;

    public TestClass(
        ILogger<ReportOnBehalfOfRequestPublisher> logger,
        IDependencyClass dependencyClass)
    {
        this.logger = logger;
        this.dependencyClass = dependencyClass;
        initializeTask = new Task(() => InitializeAsync());
    }

    private async Task InitializeAsync()
    {
        ... init this class
    }

    public async Task DoWork(IMessage message)
    {
        await this.initializeTask; // make sure we are initialized

        this.depenencyClass.DoSomething()
    }
}

The problem is that the initializeTask is in the created state using the code above. I could just call the initializeTask.Start() and wait on the task but is there a way to do this simple self-contained lazy initialization using async/await?


Solution

  • is there a way to do this simple self-contained lazy initialization using async/await?

    I think there is. The trick is to wrap lazy execute the task on the first call, thus using Lazy<T>:

    public class TestClass : ITestClass
    {
        private readonly Lazy<Task> initializeTask;
    
        public TestClass(...)
        {
            ...
            this.initializeTask = new Lazy<Task>(this.InitializeAsync);
        }
    
        private async Task InitializeAsync()
        {
            ... init this class
        }
    
        public async Task DoWork(IMessage message)
        {
            await this.initializeTask.Value; // make sure we are initialized
    
            this.depenencyClass.DoSomething()
        }
    }
    

    This ensures that InitializeAsync is just called once. Its Task will be cached for the lifetime of TestClass and any next time the Task is awaited, the code will continue immediately, because Task is already in the completed state.