Understanding C# async / await (2) Awaitable / Awaiter Pattern

What is awaitable

Part 1 shows that any Task is awaitable. Actually there are other awaitable types. Here is an example:

Task<int> task = new Task<int>(() => 0);
int result = await task.ConfigureAwait(false); // Returns a ConfiguredTaskAwaitable<TResult>.

The returned ConfiguredTaskAwaitable<TResult> struct is awaitable. And it is not Task at all:

public struct ConfiguredTaskAwaitable<TResult>
{
    private readonly ConfiguredTaskAwaiter m_configuredTaskAwaiter;

    internal ConfiguredTaskAwaitable(Task<TResult> task, bool continueOnCapturedContext)
    {
        this.m_configuredTaskAwaiter = new ConfiguredTaskAwaiter(task, continueOnCapturedContext);
    }

    public ConfiguredTaskAwaiter GetAwaiter()
    {
        return this.m_configuredTaskAwaiter;
    }
}

It has one GetAwaiter() method. Actually in part 1 we have seen that Task has GetAwaiter() method too:

public class Task
{
    public TaskAwaiter GetAwaiter()
    {
        return new TaskAwaiter(this);
    }
}

public class Task<TResult> : Task
{
    public new TaskAwaiter<TResult> GetAwaiter()
    {
        return new TaskAwaiter<TResult>(this);
    }
}

Task.Yield() is a another example:

await Task.Yield(); // Returns a YieldAwaitable.

The returned YieldAwaitable is not Task either:

public struct YieldAwaitable
{
    public YieldAwaiter GetAwaiter()
    {
        return default(YieldAwaiter);
    }
}

Again, it just has one GetAwaiter() method. In this article, we will look at what is awaitable.

The awaitable / awaiter pattern

By observing different awaitable / awaiter types, we can tell that an object is awaitable if

  • It has a GetAwaiter() method (instance method or extension method);
  • Its GetAwaiter() method returns an awaiter. An object is an awaiter if:
    • It implements INotifyCompletion or ICriticalNotifyCompletion interface;
    • It has an IsCompleted, which has a getter and returns a Boolean;
    • it has a GetResult() method, which returns void, or a result.

This awaitable / awaiter pattern is very similar to the iteratable / iterator pattern. Here is the interface definitions of iteratable / iterator:

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

public interface IEnumerator
{
    object Current { get; }

    bool MoveNext();

    void Reset();
}

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

public interface IEnumerator<out T> : IDisposable, IEnumerator
{
    T Current { get; }
}

In case you are not familiar with the out keyword, please find out the explanation in Understanding C# Covariance And Contravariance (2) Interfaces.

The “missing” IAwaitable / IAwaiter interfaces

Similar to IEnumerable and IEnumerator interfaces, awaitable / awaiter can be visualized by IAwaitable / IAwaiter interfaces too. This is the non-generic version:

public interface IAwaitable
{
    IAwaiter GetAwaiter();
}

public interface IAwaiter : INotifyCompletion // or ICriticalNotifyCompletion
{
    // INotifyCompletion has one method: void OnCompleted(Action continuation);

    // ICriticalNotifyCompletion implements INotifyCompletion,
    // also has this method: void UnsafeOnCompleted(Action continuation);

    bool IsCompleted { get; }

    void GetResult();
}

Please notice GetResult() returns void here. Task.GetAwaiter() / TaskAwaiter.GetResult() is of such case.

And this is the generic version:

public interface IAwaitable<out TResult>
{
    IAwaiter<TResult> GetAwaiter();
}

public interface IAwaiter<out TResult> : INotifyCompletion // or ICriticalNotifyCompletion
{
    bool IsCompleted { get; }

    TResult GetResult();
}

Here the only difference is, GetResult() return a result. Task<TResult>.GetAwaiter() / TaskAwaiter<TResult>.GetResult() is of this case.

Please notice .NET does not define these IAwaitable / IAwaiter interfaces at all. As an UI designer, I guess the reason is, IAwaitable interface will constraint GetAwaiter() to be instance method. Actually C# supports both GetAwaiter() instance method and GetAwaiter() extension method.

Here I use these interfaces only for better visualizing what is awaitable / awaiter. Now, if looking at above ConfiguredTaskAwaitable / ConfiguredTaskAwaiter, YieldAwaitable / YieldAwaiter, Task / TaskAwaiter pairs again, they all “implicitly” implement these “missing” IAwaitable / IAwaiter interfaces. In the next part, we will see how to implement awaitable / awaiter.

Await any function / action

In C# await cannot be used with lambda. This code:

int result = await (() => 0);

will cause a compiler error:

Cannot await 'lambda expression'

This is easy to understand because this lambda expression (() => 0) may be a function or a expression tree. Obviously we mean function here, and we can tell compiler in this way:

int result = await new Func<int>(() => 0);

It causes an different error:

Cannot await 'System.Func<int>'

OK, now the compiler is complaining the type instead of syntax. With the understanding of the awaitable / awaiter pattern, Func<TResult> type can be easily made into awaitable.

GetAwaiter() instance method, using IAwaitable / IAwaiter interfaces

First, similar to above ConfiguredTaskAwaitable<TResult>, a FuncAwaitable<TResult> can be implemented to wrap Func<TResult>:

internal struct FuncAwaitable<TResult> : IAwaitable<TResult>
{
    private readonly Func<TResult> function;

    public FuncAwaitable(Func<TResult> function)
    {
        this.function = function;
    }

    public IAwaiter<TResult> GetAwaiter()
    {
        return new FuncAwaiter<TResult>(this.function);
    }
}

FuncAwaitable<TResult> wrapper is used to implement IAwaitable<TResult>, so it has one instance method, GetAwaiter(), which returns a IAwaiter<TResult>, which wraps that Func<TResult> too. FuncAwaiter<TResult> is used to implement IAwaiter<TResult>:

public struct FuncAwaiter<TResult> : IAwaiter<TResult>
{
    private readonly Task<TResult> task;

    public FuncAwaiter(Func<TResult> function)
    {
        this.task = new Task<TResult>(function);
        this.task.Start();
    }

    bool IAwaiter<TResult>.IsCompleted
    {
        get
        {
            return this.task.IsCompleted;
        }
    }

    TResult IAwaiter<TResult>.GetResult()
    {
        return this.task.Result;
    }

    void INotifyCompletion.OnCompleted(Action continuation)
    {
        new Task(continuation).Start();
    }
}

Now a function can be awaited in this way:

int result = await new FuncAwaitable<int>(() => 0);

GetAwaiter() extension method

As IAwaitable shows, all that an awaitable needs is just a GetAwaiter() method. In above code, FuncAwaitable<TResult> is created as a wrapper of Func<TResult> and implements IAwaitable<TResult>, so that there is a  GetAwaiter() instance method. If a GetAwaiter() extension method  can be defined for Func<TResult>, then FuncAwaitable<TResult> is no longer needed:

public static class FuncExtensions
{
    public static IAwaiter<TResult> GetAwaiter<TResult>(this Func<TResult> function)
    {
        return new FuncAwaiter<TResult>(function);
    }
}

So a Func<TResult> function can be directly awaited:

int result = await new Func<int>(() => 0);

Using the existing awaitable / awaiter - Task / TaskAwaiter

Remember the most frequently used awaitable / awaiter - Task / TaskAwaiter. With Task / TaskAwaiter, FuncAwaitable / FuncAwaiter are no longer needed:

public static class FuncExtensions
{
    public static TaskAwaiter<TResult> GetAwaiter<TResult>(this Func<TResult> function)
    {
        Task<TResult> task = new Task<TResult>(function);
        task.Start();
        return task.GetAwaiter(); // Returns a TaskAwaiter<TResult>.
    }
}

Similarly, with this extension method:

public static class ActionExtensions
{
    public static TaskAwaiter GetAwaiter(this Action action)
    {
        Task task = new Task(action);
        task.Start();
        return task.GetAwaiter(); // Returns a TaskAwaiter.
    }
}

an action can be awaited as well:

await new Action(() => { });

Now any function / action can be awaited:

await new Action(() => HelperMethods.IO()); // or: await new Action(HelperMethods.IO);

If function / action has parameter(s), closure can be used:

int arg0 = 0;
int arg1 = 1;
int result = await new Action(() => HelperMethods.IO(arg0, arg1));

Using Task.Run()

The above code is used to demonstrate how awaitable / awaiter can be implemented. Because it is a common scenario to await a function / action, so .NET provides a built-in API: Task.Run():

public class Task2
{
    public static Task Run(Action action)
    {
        // The implementation is similar to:
        Task task = new Task(action);
        task.Start();
        return task;
    }

    public static Task<TResult> Run<TResult>(Func<TResult> function)
    {
        // The implementation is similar to:
        Task<TResult> task = new Task<TResult>(function);
        task.Start();
        return task;
    }
}

In reality, this is how we await a function:

int result = await Task.Run(() => HelperMethods.IO(arg0, arg1));

and await a action:

await Task.Run(() => HelperMethods.IO());

32 Comments

  • FYI: Those weird comments you are getting are mostly spammers trying to get links to their sites embedded in your blog so they get higher SEO scores.

  • whoah this blog is fantastic i love reading your articles.
    Keep up the

    good work! You know, lots of people are looking around for this

    information, you could help them greatly.

  • I am continuously searching online for posts that can assist me.
    Thx!

  • I’d have to test with you here. Which is not something I usually do!
    I get pleasure from reading a put up that can make
    people think. Also, thanks for

    allowing me to remark!

  • certainly like your web site but you have to check the

    spelling on quite a few of your posts. Several of them are rife with
    spelling issues

    and I find it very bothersome to tell the truth nevertheless I’ll certainly come


    back again.

  • When I initially commented I clicked the -Notify me
    when new comments are added-

    checkbox and now every time a remark is added I get 4 emails with the identical comment.


    Is there any method you may take away me

    from that service? Thanks!

  • You must participate in a contest for

    probably the greatest blogs on the web. I will

    recommend this web site!

  • Hi there, i read your blog occasionally and i own a similar one and i was just

    curious if you get a lot of spam responses?
    If so how do you prevent it, any plugin or anything you
    can recommend? I get so much lately it's driving me crazy so any assistance is very much appreciated.

  • Somebody essentially assist to make

    significantly posts I'd state. That is the first time I

    frequented your web page and thus far? I amazed with the

    research you made to create this actual submit

    amazing. Wonderful task!

  • Would you be involved in exchanging

    hyperlinks?

  • Hi there, just became aware of your blog through Google, and found that it is truly

    informative. I’m gonna watch out for brussels.

    I’ll be grateful if you continue

    this in future. Numerous people will be benefited from your writing.
    Cheers!

  • Hi there, simply turned into alert to your blog

    through Google, and located that it's truly informative. I am going to

    watch out for brussels. I’ll appreciate in the event you proceed this in future. Many other people shall be benefited from your writing. Cheers!

  • Hmm is anyone else experiencing problems with the pictures on this blog loading?
    I'm trying

    to determine if its a problem on my end or if it's the blog.
    Any feed-back would be greatly appreciated.

  • I am extremely impressed with your writing talents as smartly as with the format in your

    blog. Is this a paid subject matter or did you modify it

    your self? Either way stay up the excellent high quality writing, it’s

    rare to peer a great blog like this one these days..

  • A person necessarily assist to make severely articles I might state.
    That is the very first time I

    frequented your website page and up to now?
    I amazed with the

    research you made to create this actual publish extraordinary.
    Great task!

  • What’s Taking place i'm new to this, I stumbled upon this I have found It

    absolutely helpful and it has aided me out loads. I'm hoping to contribute & aid
    different users like its helped me. Great

    job.

  • I comment whenever I especially enjoy a article on a site or
    I have something to contribute to the conversation. Usually it's caused by the passion communicated in the article I looked at. And after this article Understanding C# async / await (2) Awaitable / Awaiter Pattern - Dixin's
    Blog. I was actually moved enough to drop a thought :-P I actually do
    have some questions for you if you usually do not mind.

    Could it be just me or does it give the impression like some of these remarks appear like they
    are left by brain dead visitors? :-P And, if you are writing at additional places,
    I'd like to keep up with anything new you have to post. Could you make a list every one of your shared pages like your linkedin profile, Facebook page or twitter feed?

  • I’ll immediately take hold of your rss feed as I

    can't to find your e-mail subscription hyperlink or e-newsletter service. Do

    you've any? Kindly permit me recognize in

    order that I may subscribe. Thanks.

  • Do you have a spam issue on this site; I also am a blogger,
    and I was curious about your situation; we have created some nice procedures and we

    are looking to exchange solutions with other folks, please shoot me an email if
    interested.

  • It's really a great and helpful piece of information. I'm happy that you
    simply shared this helpful info with us. Please keep us informed like this.
    Thanks for sharing.

  • Its such as you learn my mind! You seem to understand a lot about
    this, such as you wrote the ebook in it or something.
    I feel that you simply could do with a few p.c.
    to force the message house a bit, however instead of that,
    this is excellent blog. A great read. I will
    definitely be back.

  • This is my first time pay a visit at here and i am really impressed to
    read everthing at alone place.

  • This is really interesting, You're a very skilled blogger. I've joined your rss feed and look forward to seeking more of your excellent post.
    Also, I've shared your site in my social networks!

  • Magnificent goods from you, man. I have understand
    your stuff previous to and

    you are just extremely excellent. I really like what

    you've acquired here, certainly like what you're
    saying and the way in which you

    say it. You make it entertaining and you still care
    for to keep it smart. I

    can not wait to read far more from you. This is really a

    tremendous website.

  • Magnificent beat ! I would like to apprentice while you amend your site, how could i subscribe for
    a weblog website? The account helped me a appropriate deal.
    I had been tiny bit acquainted of this your broadcast offered vibrant clear concept

  • I've been surfing on-line more than 3 hours nowadays, yet I never discovered any attention-grabbing article like yours. It is lovely worth sufficient for me. In my opinion, if all site owners and bloggers made just right content as you probably did, the internet shall be much more helpful than ever before.

  • Hi to every one, it's really a pleasant for me to visit this website, it includes precious Information.

  • Great write-up, I’m regular visitor of one’s blog, maintain up the

    excellent operate, and It is going to be a regular visitor for a lengthy time.

  • L5UKAC Great blog article.Much thanks again. Awesome.

  • I'm not sure why but this website is loading extremely slow for me.
    Is anyone else having this issue or is it a problem on
    my end? I'll check back later on and see if the problem still exists.

  • AKOPLg Major thanks for the blog article.Really looking forward to read more. Cool.

  • utufqh Say, you got a nice blog. Keep writing.

Comments have been disabled for this content.