16

Say I have a class that needs to perform some async initialization using an InitializeAsync() method. I want to make sure that the initialization is performed only once. If another thread calls this method while the initalization is already in progress, it will "await" until the first call returns.

I was thinking about the following imlementation (using SemaphoreSlim). Is there a better/simpler approach?

public class MyService : IMyService
{
    private readonly SemaphoreSlim mSemaphore = new SemaphoreSlim(1, 1);
    private bool mIsInitialized;

    public async Task InitializeAsync()
    {
        if (!mIsInitialized)
        {
            await mSemaphore.WaitAsync();

            if (!mIsInitialized)
            {
                await DoStuffOnlyOnceAsync();
                mIsInitialized = true;
            }

            mSemaphore.Release();
        }
    }

    private Task DoStuffOnlyOnceAsync()
    {
        return Task.Run(() =>
        {
            Thread.Sleep(10000);
        });
    }
}

Thanks!

Edit:

Since I'm using DI and this service will be injected, consuming it as a "Lazy" resource or using an async factory won't work for me (although it could be great in other use cases). Thus, the async initialization should be encapsulated within the class and transparent to the IMyService consumers.

The idea of wrapping the initialization code in a "dummy" AsyncLazy<> object will do the job, although it feels a bit unnatural to me.

Yuval Itzchakov
  • 141,979
  • 28
  • 246
  • 306
tals
  • 183
  • 1
  • 8

4 Answers4

12

I'd go with AsyncLazy<T> (slightly modified version):

public class AsyncLazy<T> : Lazy<Task<T>> 
{ 
    public AsyncLazy(Func<T> valueFactory) : 
        base(() => Task.Run(valueFactory)) { }

    public AsyncLazy(Func<Task<T>> taskFactory) : 
        base(() => Task.Run(() => taskFactory())) { } 

    public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); } 
}

And consume it like this:

private AsyncLazy<bool> asyncLazy = new AsyncLazy<bool>(async () =>
                                    { 
                                        await DoStuffOnlyOnceAsync()
                                        return true;
                                    });

Note i'm using bool simply because you have no return type from DoStuffOnlyOnceAsync.

Edit:

Stephan Cleary (of course) also has an implementation of this here.

Theodor Zoulias
  • 24,585
  • 5
  • 40
  • 69
Yuval Itzchakov
  • 141,979
  • 28
  • 246
  • 306
  • That's Stephen Toub's [AsyncLazy](http://blogs.msdn.com/b/pfxteam/archive/2011/01/15/10116210.aspx) implementation for .NET 4.0. He describes why you need to do this instead of just calling the asynchronous method during initialization. – Panagiotis Kanavos Feb 05 '15 at 09:24
  • @PanagiotisKanavos I know. This is copied from his post in the PFX team. I've added the link :) – Yuval Itzchakov Feb 05 '15 at 09:25
  • 1
    Stephen Cleary updates this for .NET 4.5 and does away with unwrapping [here](http://blog.stephencleary.com/2012/08/asynchronous-lazy-initialization.html) – Panagiotis Kanavos Feb 05 '15 at 09:27
  • @PanagiotisKanavos They're partically the same, only update with `Task.Run` instead. I've updated the code. – Yuval Itzchakov Feb 05 '15 at 09:29
8

Yes. Use Stephen Cleary's AsyncLazy (available on the AsyncEx nuget):

private static readonly AsyncLazy<MyResource> myResource = new AsyncLazy<MyResource>(
    async () => 
    { 
        var ret = new MyResource(); 
        await ret.InitAsync(); 
        return ret; 
    }
);

public async Task UseResource()
{
    MyResource resource = await myResource;
    // ...
}

Or the visual studio SDK's AsyncLazy if you prefer a Microsoft implementation.

Theodor Zoulias
  • 24,585
  • 5
  • 40
  • 69
i3arnon
  • 107,189
  • 32
  • 306
  • 333
6

I have a blog post that covers a few different options for doing "asynchronous constructors".

Normally, I prefer asynchronous factory methods, because I think they're simpler and a bit safer:

public class MyService
{
  private MyService() { }

  public static async Task<MyService> CreateAsync()
  {
    var result = new MyService();
    result.Value = await ...;
    return result;
  }
}

AsyncLazy<T> is a perfectly good way of defining a shared asynchronous resource (and may be a better conceptual match for a "service", depending on how it is used). The one advantage of the async factory method approach is that it's not possible to create an uninitialized version of MyService.

Stephen Cleary
  • 406,130
  • 70
  • 637
  • 767
  • +1, factory methods are definitely the way to go in this scenario. I'd recommend making the factory non static so it can be easily mocked and dependency injected. – Doctor Jones May 31 '18 at 09:36
2

Stephen Toub's AsyncLazy<T> implementation is pretty nice and concise, but there are a couple of things that I don't like:

  1. In case the asynchronous operation fails, the error is cached, and will be propagated to all future awaiters of the AsyncLazy<T> instance. There is no way to un-cache the cached Task, so that the asynchronous operation can be retried.

  2. The asynchronous delegate is invoked on the ThreadPool context. There is no way to invoke it on the current context.

  3. The Lazy<Task<T>> combination generates warnings in the latest version of the Visual Studio 2019 (16.8.2). It seems that this combination can produce deadlocks in some scenarios.

  4. In the unfortunate case that the asynchronous delegate passed as argument to the Lazy<Task<T>> constructor has not a proper asynchronous implementation, and instead blocks the calling thread, all threads that will await the Lazy<Task<T>> instance will get blocked until the completion of the delegate. This is a direct consequence of how the Lazy<T> type works. This type was never designed for supporting asynchronous operations in any way.

The first issue has been addressed by Stephen Cleary's AsyncLazy<T> implementation (part of the AsyncEx library), that accepts a RetryOnFailure flag in its constructor. The second issue has also been addressed by the same implementation (ExecuteOnCallingThread flag). AFAIK the third and the fourth issues have not been addressed.

Below is an attempt to address all of these issues. This implementation instead of being based on a Lazy<Task<T>>, it uses a transient nested task (Task<Task<T>>) internally as wrapper.

/// <summary>
/// Represents a single asynchronous operation that is started on first demand.
/// In case of failure the error is not cached, and the operation is restarted
/// (retried) later on demand.
/// </summary>
public class AsyncLazy<TResult>
{
    private Func<Task<TResult>> _factory;
    private Task<TResult> _task;

    public AsyncLazy(Func<Task<TResult>> factory)
    {
        _factory = factory ?? throw new ArgumentNullException(nameof(factory));
    }

    public Task<TResult> Task
    {
        get
        {
            var currentTask = Volatile.Read(ref _task);
            if (currentTask == null)
            {
                Task<TResult> newTask = null;
                var newTaskTask = new Task<Task<TResult>>(async () =>
                {
                    try
                    {
                        var result = await _factory().ConfigureAwait(false);
                        _factory = null; // No longer needed (let it get recycled)
                        return result;
                    }
                    catch
                    {
                        _ = Interlocked.CompareExchange(ref _task, null, newTask);
                        throw;
                    }
                });
                newTask = newTaskTask.Unwrap();
                currentTask = Interlocked
                    .CompareExchange(ref _task, newTask, null) ?? newTask;
                if (currentTask == newTask)
                    newTaskTask.RunSynchronously(TaskScheduler.Default);
            }
            return currentTask;
        }
    }

    public TaskAwaiter<TResult> GetAwaiter() { return this.Task.GetAwaiter(); }
}

Usage example:

var deferredTask = new AsyncLazy<string>(async () =>
{
    return await _httpClient.GetStringAsync("https://stackoverflow.com");
});

//... (the operation has not started yet)

string html = await deferredTask;

The factory delegate is invoked on the current thread. If you prefer to invoke it on the ThreadPool, just replace the RunSynchronously with the Start. Normally an asynchronous delegate is expected to return quickly, so invoking on the current thread shouldn't be a problem. As a bonus it opens the possibility of interacting with thread-affine components, like UI controls, from inside the delegate.

Theodor Zoulias
  • 24,585
  • 5
  • 40
  • 69