Writing “Lazy Task” Using New Features of C# 7

Image for post
Image for post

Almost 100% of asynchronous tasks that you deal with in your C# code are running or already completed regardless of whether you “await” them or not. This can be shown with the following example:

static async Task Main()
{
var task = GetValueAsync();
await Task.Delay(500);
Console.WriteLine("Awating...");
Console.WriteLine("Result is: " + await task);
}
static async Task<int> GetValueAsync()
{
Console.WriteLine("Async Start");
await Task.Delay(100);
Console.WriteLine("Async End");
return 42;
}

Output:

Async Start
Async End
Awating...
Result is: 42

As you see GetValueAsync finished before await but what if the behavior is not desired and you do not want a task to start executing until there is at least one “awaiter”? Of course, you can use Lazy — a generic class from the System namespace:

static async Task Main()
{
var task = new Lazy<Task<int>>(GetValueAsync);
await Task.Delay(500);
Console.WriteLine("Awating...");
Console.WriteLine("Result is: " + await task.Value);
}
static async Task<int> GetValueAsync()
{
Console.WriteLine("Async Start");
await Task.Delay(100);
Console.WriteLine("Async End");
return 42;
}

Output:

Awating...
Async Start
Async End
Result is: 42

However, there is another solution which will just slightly impact you client code — it is replacing of Task returned by your asynchronous function with LazyTask — a class that you can create by your own using the new C#7 feature — Generalized async return types

As a result, your code will look like as follows:

static async Task Main()
{
var task = GetValueAsync();
await Task.Delay(500);
Console.WriteLine("Awating...");
Console.WriteLine("Result is: " + await task);
}
static async LazyTask<int> GetValueAsync()
{
Console.WriteLine("Async Start");
await Task.Delay(100);
Console.WriteLine("Async End");
return 42;
}

Output:

Awating...
Async Start
Async End
Result is: 42

As you know C# compiler converts methods marked async into a state machine where each state represents a completion of an asynchronous operation marked await. Generalized async return types allow you to gain access to the state machine execution (I examined this mechanism in detail in one of my previous articles.) and in our case we just need to postpone the first step till we have at least one “awaiter”. It can be done by adjusting the Start method:

public class LazyTaskMethodBuilder<T>
{
public LazyTaskMethodBuilder() => this.Task = new LazyTask<T>();
public void Start<TStateMachine>(
ref TStateMachine stateMachine)..
{
//instead of stateMachine.MoveNext();
this.Task.SetStateMachine(stateMachine);
}
...
}

Instead of starting the first step, the Start method just stores a link to the state machine inside the LazyTask object.

The first step now can be called only when we have the first “awaiter”:

public void OnCompleted(Action continuation)                               {
...
this._asyncStateMachine.MoveNext();
...
}

Note: The interesting thing that the trick works even with methods which do not have any asynchronous calls inside (without “await” keyword).

That’s all!

In conclusion, I want to say that Generalized async return types is a powerful mechanism and LazyTask is just one the examples of how it can be utilized.

Links

  1. Source code on Gitnub;

2. “Maybe” monad through async/await in C# (No Tasks!) — an article where Generalized async return types are examined in detail.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store