AsyncLazy<T>

AsyncLazy<T>

Rate This
  • Comments 10

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!

Leave a Comment
  • Please add 6 and 1 and type the answer here:
  • Post
  • I think plastering everything with GetAwaiter is not very clean. It is like ruby "5.times { |i| puts i }". The times method does not logically belong to Int32 at all.

  • This thought actually came to mind in the last article "await anything". Does it seem right to you to await ... a TimeSpan? This would only be appropriate in a very specific scenario where you have to await a TimeSpan 100th of times all over your code. The disadvantage is that it is one additional concept that people have to learn, but unnecessarily so. What is wrong with await Task.Delay()? It is like operator overloading, but misused.

  • Hi Tobi-

    I'm not advocating that everything have a GetAwaiter method.  I'm just highlighting that and how it can be done.  It's very similar to how you can add a GetEnumerator method to anything, and there, too, it shouldn't be done willy nilly.  As I state at the end of the "await anything;" post, just because you can doesn't mean you should: "Just keep in mind that while there are plenty of 'cool' things you can do, code readability and maintainability is really important, so make sure that the coolness isn’t trumped by lack of clarity about the code’s meaning."

  • I was going to make an F# version of this class, but then I realized that language features make it unnecessary. "AsyncLazy<T> foo = new AsyncLazy<T>(() => my code)" is actually more typing than simply, "let foo = lazy(async{ my code })"

  • Hi Joel-

    Thanks. The definition of the class was simply to help explore the topic.  The same thing is of course possible in other languages, such as using C# and Visual Basic's new async functionality:

    var foo = new Lazy<Task<T>>(async delegate { /* my code */ });

    Then you're just dealing with small differences in typing due to syntax.

  • Here is another implementation of async lazy

    16handles.wordpress.com/.../asynchronous-lazy-initialization

  • I'm just wandering why the second constructor overload is:

    base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap())

    Instead of:

    base(taskFactory)

  • @Michael: That's discussed in the post... search the text for "With this function, we have two good options for how to deal with it", as that begins the explanation.  I hope that helps.

  • Hi,

    shouldn't the line

    public TaskAwaiter GetAwaiter() { return Value.GetAwaiter(); }

    be

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

    ?

  • @Takeshi: Yup, thanks for pointing out the typo... fixed.

Page 1 of 1 (10 items)