AsyncLazy

Stephen Toub - MSFT

A question I’ve seen come up from time to time is “Why doesn’t Lazy<T> support asynchronous initialization?”  It’s a fair question.  After all, if you’re using Lazy<T> because you have an expensive resource you want to delay the creation of until it’s absolutely needed, it’s fair to reason that the creation of that resource could also be time-consuming, and thus you don’t necessarily want to block the requesting thread while the resource is being initialized (of course, a Lazy<T> may also be used not because the T takes a long time to create, but because its hefty from a resource consumption perspective).  This would be particularly true if the value factory for the Lazy<T> does I/O in order to initialize the data, such as downloading from a remote web site.

The answer as to why Lazy<T> doesn’t have built-in asynchronous support iis that Lazy<T> is all about caching a value and synchronizing multiple threads attempting to get at that cached value, whereas we have another type in the .NET Framework focused on representing an asynchronous operation and making its result available in the future: Task<T>.  Rather than building asynchronous semantics into Lazy<T>, you can instead combine the power of Lazy<T> and Task<T> to get the best of both types!

Let’s take just a few lines of code to put the two together:

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

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

Here we have a new AsyncLazy<T> that derives from Lazy<Task<T>> and provides two constructors.  Each of the constructors takes a function from the caller, just as does Lazy<T>.  The first constructor, in fact, takes the same Func<T> that Lazy<T>.  Instead of passing that Func<T> directly down to the base constructor, however, we instead pass down a new Func<Task<T>> which simply uses StartNew to run the user-provided Func<T>.  The second constructor is a bit more fancy.  Rather than taking a Func<T>, it takes a Func<Task<T>>.  With this function, we have two good options for how to deal with it.  The first is simply to pass the function straight down to the base constructor, e.g.

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

That option works, but it means that when a user accesses the Value property of this instance, the taskFactory delegate will be invoked synchronously.  That could be perfectly reasonable if the taskFactory delegate does very little work before returning the task instance.  If, however, the taskFactory delegate does any non-negligable work, a call to Value would block until the call to taskFactory completes.  To cover that case, the second approach is to run the taskFactory using Task.Factory.StartNew, i.e. to run the delegate itself asynchronously, just as with the first constructor, even though this delegate already returns a Task<T>.  Of course, now StartNew will be returning a Task<Task<T>>, so we use the Unwrap method in .NET 4 to convert the Task<Task<T>> into a Task<T>, and that’s what’s passed down the base type.

With the implementation out of the way, we can now observe the power this holds, especially when mixed with the new language support in C# and Visual Basic for working with tasks asynchronously.  As a reminder, Lazy<T> exposes a Value property of type T.  The first time Value is accesses, the valueFactory delegate is invoked to get that T value, which is returned from Value on this and all subsequent calls to Value.  By default, if multiple threads access Value concurrently, the first thread to get to Value will cause the delegate to be invoked, and all other threads will block until the T value has been computed.  In the case of AsyncLazy, the T is actually a Task<T>.

Let’s say in our program we have one of these AsyncLazy instances:

static string LoadString() { … }

static AsyncLazy<string> m_data = new AsyncLazy<string>(LoadString);

Now elsewhere in my code I need the result of accessing LoadString.  The work to load the string could take some time, and I don’t want to block the accessing thread waiting until the data has been loaded.  We can thus access m_data.Value to get the Task<string> which will complete with the loaded string data when it’s available.  Since this is a standard Task<string>, I have all of the facilities available to me that Task<string> provides, including the ability to synchronously wait for the data (e.g. Wait, Result), or to asynchronously be notified when the data is available (e.g. ContinueWith).  With the new language support, we also have “await”.  Thus, we can write an asynchronous method that does:

string data = await m_data.Value;

A few interesting things to note about this.  First, the Value property will return very quickly, as all the first access to Value does is run the delegate which just launches a task and returns it, and all subsequent accesses will just return that cached task.  Thus, we quickly get back a Task<string> which we can await, allowing the current thread to be used for other work while the data is being loaded.  Second, and subtle, this operation is very efficient once the data has been loaded.  Task<string> stores the result once it’s available, and accessing its Result property (which Task<T>’s support for await does) just returns that stored value.  Further, the code generated by await makes a call into the task’s TaskAwaiter as returned by the task’s GetAwaiter method.  The TaskAwaiter value returned by GetAwaiter has a BeginAwait method on it, which is called by the compiler-generated code for the await expression.  BeginAwait first checks whether the task has already completed, and if it is, it simply returns false from BeginAwait, which tells the compiler-generated code that there’s no need to do anything fancy for asynchronicity, and rather than the code can just continue executing.  In other words, once the lazy value is available, the “await” in effect just goes away.

With our second constructor on the AsyncLazy<T> type, we can also pass an asynchronous method as the taskFactory delegate.  Remember that a method marked with the new async keyword can return either void, Task, or Task<T>, and just as an anonymous method that returns T can be treated as a Func<T>, so too can an anonymous method that returns Task<T> be treated as a Func<Task<T>>, which just so happens to be the same type as taskFactory.  Thus, we can write code like the following:

static AsyncLazy<string> m_data = new AsyncLazy<string>(async delegate
{
    WebClient client = new WebClient();
    return (await client.DownloadStringTaskAsync(someUrl)).ToUpper();
});

Here, not only is a consumer of the m_data instance not going to block a thread when they “await m_data.Value”, but the initialization routine will also not block any threads while waiting for the data to be asynchronously downloaded from the Web site.

One last thought on the subject.  In a previous post, we talked about how the C# and Visual Basic compilers allow you to await anything that exposes the right pattern of methods.  If you don’t like the thought of having to type “.Value” every time you access your AsyncLazy<T> instance, well then you can simply augment AsyncLazy<T> with a one-line GetAwaiter method:

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

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

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

Now, for the previous m_data example, instead of writing:

string result = await m_data.Value;

we can simply write:

string result = await m_data;

and it’ll have the same effect. 

Enjoy!

5 comments

Discussion is closed. Login to edit/delete existing comments.

  • Eric Rey 0

    Hi Stephen, I know this article is from 2011, but I just found it. Thank you, it is very interesting.

    I needed a way to create an async and lazily executed pipeline in C#, with no side effects during its construction, and came up with this: https://gist.github.com/ericrey85/da9671a22234ef981e5ee3653face4af

    Would love to get your feedback.
    Thank you.

  • Arnaud Boussaer 0

    Hi Stephen, I have a question about your explanation about the AsyncLazy contructor:
    Why is the following code not good enough for long running calls:

    public AsyncLazy(Func<Task> taskFactory) : base(taskFactory) { }

    Above you say that “a call to Value would block until the call to taskFactory completes” and your solution is to wrap the factory in a “Task.Factory.StartNew”.
    But I don’t see why the thread would be blocked when you call the lazy.Value. You are already working with a Task and if the user code uses the await keyword, would that not result in “await lazyAsyncTask()”, and be non-blocking? Pseudo code:
    asyncLazy(taskfactory)
    -> Lazy(taskFactory)
    -> Lazy.Value returns Task foo
    -> usercode: await foo

    Reason for this question: wrapping inside a new thread results in losing the context (httpContext)

    • Stephen Toub - MSFTMicrosoft employee 0

      Consider if the Iambda provided were:

      async () =>
      {
          Thread.Sleep(10_000);
          await Whatever();
      }

      That would block the invocation of the delegate synchronously for 10 seconds before the caller was even handed back a Task.

      • Arnaud Boussaer 0

        Hi Stephen,
        First of all, thank you for your response.

        Based on your example above, is my following assumption correct:
        Your AsyncLazy implementation is a class which executes all delegates asynchronous in a parallel thread. Even if the provided delegate contains synchronous code, your implementation will execute it without blocking the invoking thread.

        I saw AsyncLazy as a simple extension of Lazy which allows to execute an async delegate.
        In your answer above, it seems logic to me that the invoking thread is blocked because the user ‘wanted that’ when writing the synchronous code Thread.Sleep(). If the user would like to execute his synchronous code in a parallel thread, he could write that himself, I think…

        An interesting opinion is posted on the dotnet feature request ticket for AsyncLazy:
        https://github.com/dotnet/runtime/issues/27510#issuecomment-711037637
        Specifically point ‘2’ in his comment where he gives advise when he would implement AsyncLazy from a clean page. He mentions the same reason as I (needing the context) for a default behaviour.

        • Stephen Toub - MSFTMicrosoft employee 0

          > If the user would like to execute his synchronous code in a parallel thread, he could write that himself

          Yes. But then again, this whole thing is unnecessary from that perspective, as then a user could just write themselves:

          var lazy = new Lazy<Task<T>>(() => ...);
          ...
          T result = await lazy.Value;

          So any choices made around creating a reusable version of that one-liner involves policy decisions.

Feedback usabilla icon