Awaiting anything with GetAwaiter

Using a GetAwaiter extension method to await any type

Home DailyDrop

Daily Knowledge Drop

All that is required to await a specific type, is that the type has a method called GetAwaiter which returns an instance of TaskAwaiter.

This GetAwaiter method can be an extension method - which means any type can be extended to be awaited.


Use case: double parse

In general, the below use cases are not a recommendation or proposed best technique for solving the use case - but it's interesting to discover what is possible.

In this use case, we are going to attempt to make an async version of the double.TryParse method.


TryParseAsync

This solution doesn't require the usage of the GetAwaiter method - in this first option we are just going to create a method which returns a Task (and as such, the Task is awaited):

// extension method on string
public static Task<bool> TryParseAsync(this string s, out double result)
{
    // perform the normal TryParse
    if(double.TryParse(s, out result))
    {
        // return a Task instead of the 
        // TryParse response
        return Task.FromResult(true);
    }
    
    // return a Task instead of the 
    // TryParse response
    return Task.FromResult(false);
}

This can now be used as follows:

// Instead of doing it this OLD way
if(double.TryParse("100", out double result))
{
    Console.WriteLine("100 is a double");
}

// This is now possible:
// The TryParseAsync is called directly 
// on the string and can be awaited
if(await "100".TryParseAsync(out double result1))
{
    Console.WriteLine("100 is a double");
}

Here, the TryParseAsync can be used on a string value directly, and as it returns a Task, the call can be awaited.


await directly

However, what if there is a requirement to perform the same functionality as TryParseAsync, but to await the string directly - not call the TryParseAsync method. Well, no problem!

public static TaskAwaiter<bool> GetAwaiter(this string s)
{
    return s.TryParseAsync(out double _).GetAwaiter();
}

Here, a GetAwaiter extension method is created on string - by convention, this now allows string to be awaited directly. In this instance, awaiting a string will try parse it as a double (using the previously created extension method).

So now this can be done:

// just await the string
if(await "500")
{
    Console.WriteLine("500 is a double");
}

As mentioned - this is not really a recommended approach at all - while the code is more concise, the readability is not great, with the code making no contextual sense. However, it is what can be done.


Use Case: TimeSpan

A more useful use case, is to expand the functionality of TimeSpan to make it easier to wait for specific lengths of time.

Delay

Generally when a delay is required in code, a variation of the following is used:

await Task.Delay(TimeSpan.FromMilliseconds(100));

Nothing wrong with this, it works. However if used often throughout code, it's it fairly verbose.


await

As before, a GetAwaiter method could be added to TimeSpan to make it awaitable, eliminating the need for the Task.Delay (this will still be required, it will just be wrapped up in the extension method):

// extension method on TimeSpan
public static TaskAwaiter GetAwaiter(this TimeSpan timeSpan)
{
    return Task.Delay(timeSpan).GetAwaiter();
}

This method can now be used as follows:

await TimeSpan.FromMilliseconds(100);

Definitely simpler and more concise than the previous version, and I'd argue actually more readable.


await extended

This could even be taken a step further with an addition extension method:

public static TimeSpan MilliSeconds(this int i) => TimeSpan.FromMilliseconds(i);

This is an extension method on an int, which will convert the integer to an equivalent TimeSpan object.

As we already have an existing extension method to await a TimeSpan, the two extensions methods can be used in conjunction - convert an integer to a TimeSpan, which can then be awaited:

await 100.MilliSeconds();

Notes

While this is a very useful and convenient technique to add the await functionality to any class, it doesn't mean every class should have this functionality. In the case of TryParseAsync, the code was made less readable, for no real benefit. In the case of TimeSpan, the await extension to the class did add value to the developer. Basically, made an informed choice to use GetAwaiter, and only do so where it makes sense.


References

Cursed C# - Doing shenanigans in C#

Daily Drop 199: 10-11-2022

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net await task