C# 5 async and choosing terminology: why I’m against "yield"

Eric Lippert’s latest (at the time of this writing) blog post asks for suggestions for alternatives to the new "await" keyword, which has inappropriate connotations that confuse people. In particular, it sounds like the thread is going to sit there waiting for the result, when the whole point is that it doesn’t.

There have been many suggestions in the comments, and lots of them involve "yield". I was initially in favour of this too, but on further reflection I don’t think it’s appropriate, for the same reason: it has a connotation which may not be true. It sounds like it’s always going to yield control, when sometimes it doesn’t. To demonstrate this, I’ve come up with a tiny example. It’s a stock market class which allows you to compute the total value of your holdings asynchronously. The class would make web service calls to fetch real prices, but then cache values for some period. The caching bit is the important part here – and in fact it’s the only part I’ve actually implemented.

The point is that when the asynchronous "total value" computation can fetch a price from the cache, it doesn’t need to wait for anything, so it doesn’t need to yield control. This is the purpose of the return value of BeginAwait: if it returns false, the task has been completed and EndAwait can be called immediately. In this case the continuation is ignored – when BeginAwait returns, the ComputeTotalValue method keeps going rather than returning a Task to the caller.

Here’s the complete code:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

class Test
{
    static void Main()
    {
        StockMarket market = new StockMarket();
        market.AddCached("GOOG", 613.70m);
        market.AddCached("MSFT", 26.67m);
        market.AddCached("AAPL", 300.98m);
        
        Task<decimal> total = market.ComputeTotalValueAsync(
            Tuple.Create("AAPL", 10),
            Tuple.Create("GOOG", 20),
            Tuple.Create("MSFT", 25)
        );
        Console.WriteLine("Completed already? {0}", total.IsCompleted);
        Console.WriteLine("Total value: {0}", total.Result);
    }
}

class StockMarket
{
    private readonly Dictionary<string, decimal> cache =
        new Dictionary<string, decimal>();
    
    internal void AddCached(string ticker, decimal price)
    {
        cache[ticker] = price;
    }
    
    public async Task<decimal> ComputeTotalValueAsync(params Tuple<string, int>[] holdings)
    {
        // In real code we may well want to parallelize this, of course
        decimal total = 0m;
        foreach (var pair in holdings)
        {
            total += await new StockFetcher(this, pair.Item1) * pair.Item2;
        }
        Console.WriteLine("Diagnostics: completed ComputeTotalValue");
        return total;
    }
    
    private class StockFetcher
    {
        private readonly StockMarket market;
        private readonly string ticker;
        private decimal value;
        
        internal StockFetcher(StockMarket market, string ticker)
        {
            this.market = market;
            this.ticker = ticker;
        }
        
        internal StockFetcher GetAwaiter()
        {
            return this;
        }
        
        internal bool BeginAwait(Action continuation)
        {
            // If it’s in the cache, we can complete synchronously
            if (market.cache.TryGetValue(ticker, out value))
            {            
                return false;
            }
            // Otherwise, we need to make an async web request and do
            // cunning things. Not implemented :)
            throw new NotImplementedException("Oops.");
        }
        
        internal decimal EndAwait()
        {
            // Usually more appropriate checking here, of course
            return value;
        }
    }
}

(Note that we’d probably have a public method to fetch a single stock value asynchronously too, and that would probably return a task – in this case I wanted to keep everything as simple as possible, not relying on any other implementation of asynchrony. This also shows how the compiler uses GetAwait/BeginAwait/EndAwait… and that they don’t even need to be public methods.)

The result shows that everything was actually computed synchronously – the returned task is complete by the time the method returns. You may be wondering why we’ve bothered using async at all here – and the key is the bit that throws the NotImplementedException. While everything returns synchronously in this case, we’ve allowed for the possibility of asynchronous fetching, and the only bit of code which would need to change is BeginAwait.

So what does this have to do with the choice of keywords? It shows that "yield" really isn’t appropriate here. When the action completes very quickly and synchronously, it isn’t yielding at all.

What’s the alternative?

There are two aspects of the behaviour of the current "await" contextual keyword:

  • We might yield control, returning a task to the caller.
  • We will continue processing at some point after the asynchronous subtask has completed – whether it’s completed immediately or whether our continuation is called back.

It’s hard to capture both of those aspects in one or two words, but I think it make sense to at least capture the aspect which is always valid. So I propose something like "continue after":

foreach (var pair in holdings) 

    total += continue after new StockFetcher(this, pair.Item1) * pair.Item2; 
}

I’m not particularly wedded to the "after" bit – it could be "when" or "with" for example.

I don’t think this is perfect – I’m really just trying to move the debate forward a bit. I think the community doesn’t really have enough of a "feeling" for the new feature yet to come up with a definitive answer at the moment (and I include myself there). I think focusing on which aspects we want to emphasize – with a clear understanding of how the feature actually behaves – it a good start though.

39 thoughts on “C# 5 async and choosing terminology: why I’m against "yield"”

  1. I don’t necessarily disagree, but here’s a counterpoint to continue the debate:

    “continue after” has an intuitive read when the async method is returns void, but much less so when it returns a value.

    foo.bar = baz(continue after SomeAsync()))

    There’s an arbitrary amount of the continuation that may occur lexically ‘before’ the ‘continue’.

    ———

    The more I think about alternative names, the more I’m convinced that F#’s handling of ‘await’ as a binding construct rather than an expression is more likely to ‘guide the programmer into the pit of success’.

    Perhaps for C# it should in the style of a using statement, i.e.

    continue with (var foo = BarAsync()) {
    statement
    }

    (Or if it doesn’t introduce and syntax breakage, stike the ‘with’ entirely.)

  2. I wouldn’t like too many or long words between the assignment and the call, in you example it’s three words: continue after new

    I think [do] might work:

    total += do new StockFetcher(this, pair.Item1) * pair.Item2;

    And I do believe that the calling code yields. What looks like a single call is actually wrapped in a way that GetAwaiter, BeginAwait and EndAwait are all called. Seems like the code is yielding some of its control, even if the value is actually returned immediately in this example.

  3. I prefer something that makes really clear that the control is returned to the caller method.
    The best option that I saw in Eric’s post comments is:

    return until

  4. @Filini: I think you’ve missed the point of my post. In this case control *isn’t* returned to the caller method because the task has actually completed synchronously.

  5. @Filini & @skeet:

    Control is _never_ returned to the caller at an ‘await’, whether the method completes synchronously or not.

    The fact that the thread is free to execute other work from a different flow of control is entirely orthagonal to when control is returned to the caller. As such, anything involving ‘return’ would be way off base.

  6. It seems that the correct keyword would be declarative, i.e., capture the *intent* of the programmer. I like ‘yield while’ or ‘yield until’ because it captures such an intent: ‘I am willing to yield control until the following asynchronous method returns’. Now, the implementation may not yield control because of a race/performance optimization, but still, conveying the programmer’s intent should take precedence over conveying an implementation detail.

  7. @skeet: Your are right (but I was giving my suggestion for the whole feature, not about your post case specifically). But in the other case (the task is actually taking some time, and I suspect it would be the most frequent case, if I set up an async pattern in my code) it does.

    The problem here is that the feature does 2 different things wether the task is completed immediately or not.

    It’s hard to express “go on, or return and resume later” with one or two words.

    I’m not even close to be an expert in language design stuff, but I’m starting to have the impression that this particular syntactic sugar may be hiding too much code…

  8. I don’t like the use of continue/resume/await, because they give the impression ‘I will continue execution after this task is done’, And doesn’t capture the idea that ‘I will run the remainder of this method when the task is done’

    I’ve already mentioned anywhere, but my choice would be to reuse the ‘async’ keyword in the call to the async method. You’re then saying “run this code asyncrhonously”. It doesn’t have the connotation that any waiting happens, and is ambiguous as to whether the code returns to the caller or the continuation is run.

    Another option would be something like “yield async” or “continue async”. They both mean the same thing.

    I put elsewhere another reason to use async in the call too. The compiler could add some sugar in the same way it does for attributes, and detect “Async” in the method name rather than you needing to put it there manually. End result might be something like ‘async DownloadFile(..)’ rather than ‘async DownloadFileAsync(…)’

  9. Like Ilia Jerebtsov, I think “after” might be a nice one, because it’s short, descriptive and leaves open what is happening in the meantime.

    So you get something like:

    total += after new StockFetcher(this, pair.Item1) * pair.Item2;

    Sure it doesn’t really look nice in English, but we’re writing code, not a novel ;)

  10. @Sparkie: I wonder why no-one thought of using async there too.

    As it is part of the same ‘feature’, I think it totally makes sense.

    (And that all the other suggestions seem imprecise)

  11. @Blake: Either you’ve misunderstood async or I have :) I believe that control is returned to the caller the first time a call to BeginAwait returns true. Will double check and write another post about this tonight.

  12. Well yes.. and no.

    If we follow your line of thought, isn’t the use of the keyword async also misleading, as in your code it is run synchronously?

    One thing does seem to be clear – it’s very difficult to come up with one word which satisfies anybody. In fact, the only one word alternative which I’ve felt is any good so far is async (in the place of await, and drop the async on the method)…

    I started a poll here, for anyone who’s motivated enough to post/vote there (Eric does lurk, so you won’t be 100% wasting your time :)

  13. “Either you’ve misunderstood async or I have :) I believe that control is returned to the caller the first time a call to BeginAwait returns true.”

    Blake has misunderstood. Anders made this point crystal clear in his PDC conversation with Charlie. He even specifically called out the scenario where no real async work is done, the thread does not yield, and control in the thread only returns to the caller after the “awaited” work is done.

  14. I really shouldn’t have included the “_never_” above, as it isn’t literally true but tried to clarify the point I was making over on Eric’s blog:

    “Async methods are meant to be composed.

    Just as the async method you are writing uses “await” to call various async methods in it’s body, the caller will be using ‘await’ to call you. This is the primary benefit, letting the whole call tree be structured in a simple, easy-to-reason-about fashion.

    Only at the very bottom (where asyncs are being built out of platform primitives), and at the very top (where an entire async workflow is being kicked off with a TaskEx.Run or such) will this pattern not hold true.”

    This issue may be at the heart of why it’s difficult to choose an optimal keyword. It depends on whether you are approaching it from how it is implemented or how it is intended to be used.

  15. For me the best term is “watch” like in watchman or watchtower. For me, the statement says “continue the code while I watch that variable here”

  16. “I really shouldn’t have included the “_never_” above, as it isn’t literally true but tried to clarify the point I was making over on Eric’s blog:”

    But, to be clear, even in the context of what Eric wrote, it’s entirely possible (and in some scenarios, not even uncommon) that the “async” method that is called will in fact return immediately, rather than yielding.

    This can happen, for example, if the method is used to retrieve data that could be cached. If the data is in the cache already, it’s likely the implementation would just return the data immediately, rather than going through the whole async pipeline.

    IMHO it is important to not conflate this new feature with true asynchronous or concurrent behavior (those two not exactly the same either, though closely related). These new keywords are most useful in an asynchronous completion context, but they do not _require_ asynchronous completion, and in fact in a non-trivial number of cases, won’t actually involve asynchronous completion. They simply _allow_ for asynchronous completion without the added complication in code that is required today to support that.

  17. I am not too hung up on what the actual keyword is. My dog in the fight is that the proposed syntax embeds the keyword in an otherwise perfectly valid expression. In keeping with the spirit of iterator blocks the keyword should precede the continuation statement. Afterall, “yield return value” is legal, but “return yield value” is not. Of course, there would have to be some kind of provision for capturing the return value.

  18. If we have to have a brand new keyword, I like “yield until” best. If the call completes immediately, then you have “yield until now” which yields for no time at all, I don’t find that hard to understand.

    Another point is that Eric doesn’t like “await” because it suggests blocking and he feels that’s inaccurate. It’s not, because it does block this thread of execution. Not an OS thread, but a lightweight thread of execution.

    Overall, though, it would be better to reuse existing syntax instead of inventing new keywords to compete for programmer awareness. Maybe C# could borrow from Dynamic C’s syntax for coroutines?(http://www.rabbit.com/documentation/docs/manuals/DC10/DCUserManual/5multi.htm)

  19. Since we are dealing with Task‘s, how about “start” with the keyword preceding the statement, as in:

    start total += new StockFetcher(this, pair.Item1) * pair.Item2;

  20. @Ron: That sounds like the task is only starting at that point though. You can await something that has been started ages beforehand.

  21. @skeet: Yes, “start” doesn’t fully describe what is happening. How about “finish”, as in:

    finish total += new StockFetcher(this, pair.Item1) * pair.Item2;

    If “finish” doesn’t quite sound right, how about: achieve, complete, attain, acquire, reap

  22. How about “async” ???

    That is:

    public async Task ComputeTotalValueAsync(params Tuple[] holdings)
    {

    total += async new StockFetcher(this, pair.Item1) * pair.Item2;
    }

    }

  23. @Ron: “finish” is definitely better than “start” but I think “await” is better than any of the other alternatives listed, to be honest.

    @Mark: Reusing async would certainly be an interesting option. Not sure how I feel about that.

  24. so running an Async method without ‘await’ actually breaks the control flow in my opinion.

    just assume someone build a great library with only async public methods, and omitted the ‘Async’ appendix (which is just a convention).

    also this changed behaviour/breaks code, when the messages aren’t renamed.

    so this could lead to people requesting another keyword, to run that really asynchronously.

    I have though about turning it around, because I imagine in the future most APIs will be async, and we have ‘await’ all over the place.

    so why not make waiting the default, and explicitely tell the flow to continue when using (for example) ‘async’-keyword before calling a method?

    would love to hear your thoughts.
    1) how badly may programs behave, when some method now run async?
    2) why not make “awaiting” default/keyword-less?

  25. @mike: I’m not really with you. Bear in mind that these async methods would have to return Task rather than T, so it’s not like you could *accidentally* run them and not be aware of it at all. Admittedly void methods *could* do this… but then methods have always had the ability to start other threads – how is this particularly different?

    I’m not sure that I’d say that *most* APIs will be async. If they need IO or will be running a significant task (for one particular method call), then maybe… but I find that a fair chunk of the time that’s not the case. I certainly hope that all network-related services expose their APIs as async ones though.

    I’m sure there will be people who abuse async (and I hope to find amusing ways to deliberately abuse it myself) but I think it’ll work out just fine for the most part, when everyone’s got their heads round it.

  26. How about “result of” and/or “completion of”? As in:

    total += result of new StockFetcher(this, pair.Item1) * pair.Item2;

  27. How about ‘eventually’?

    It expresses the nondeterminism (may execute immediately, may not) and isn’t easily confused with other behaviors.

    Of course, grammatically it would need to /follow/ the task it’s executing, rather than precede it, but I think it’s cute:

    public async Task ComputeTotalValueAsync(params Tuple[] holdings)
    {
    decimal total = 0m;
    foreach (var pair in holdings)
    {
    total += new StockFetcher(this, pair.Item1) eventually * pair.Item2;
    }
    Console.WriteLine(“Diagnostics: completed ComputeTotalValue”);
    return total;
    }

    If the task to run includes more than one statement, then it’s a statement block:

    total += { new StockFetcher(this, pair.Item1) * pair.Item2 } eventually;

Comments are closed.