4

Trying to understanding why .NET implemented async-await they way they did.

When changing a simple section of code to use async-await, it seems like the least required effort is to mark both the calling and the called methods with async-await directives:

private async void OnGuiClick(object sender, EventArgs args)
{
  textBox1.Text = await Work();
}

private async Task<string> Work()
{
  await Task.Delay(2000);
  return "Result";
}

Why does .NET insist on both? i.e. It would have been nice to specify with a single keyword, that an expression must immediately be evaluated asynchronously on a worker thread - while the calling GUI thread is freed to perform other tasks, but will be hooked up again to execute the remaining code once the worker thread is done.

Is there a simpler or better way of assigning a worker thread to process the Work method, and then automatically (without needing to resort to Invoke(...)) ensure the same calling GUI thread processes the result? Why not something like this:

private void OnGuiClick(object sender, EventArgs args)
{
  textBox1.Text = <'some async directive'> Work();
}

private string Work()
{
  Thread.Sleep(2000);
  return "Result";
}

(The MSDN documentation states that the compiler will execute code synchronously if the target does not contain an await statement - but then what's the point? - Surely the await async keywords are only intended for asynchronous usage. Then why make it so complicated, instead of using a single directive?)

Fortmann
  • 433
  • 6
  • 20
  • 1
    Eric Lippert posted a decent [blog post](http://blogs.msdn.com/b/ericlippert/archive/2010/10/29/asynchronous-programming-in-c-5-0-part-two-whence-await.aspx) some time back that may be worth reading, to wit: "The “async” modifier on the method does not mean “this method is automatically scheduled to run on a worker thread asynchronously”" and "The whole point of async methods it that you stay on the current thread as much as possible." – Damien_The_Unbeliever Jan 28 '14 at 08:27
  • Thanks - that article really helped : await does NOT create a worker thread, it uses the current thread. And async does NOT guarantee asynchronous implementation (even though compiler will warn), it is up to the developer to make sure the async method is properly asynchronous. – Fortmann Jan 28 '14 at 09:14
  • possible duplicate of [Why does the async keyword exist](http://stackoverflow.com/questions/9225748/why-does-the-async-keyword-exist) – Stephen Cleary Jan 28 '14 at 13:52
  • @StephenCleary, partially agree - but my real question is not the same as indicated SO topic. The additional advice and explanation in this topic was helpful, but what I really want is described in my comment to your answer below – Fortmann Jan 28 '14 at 16:54

3 Answers3

3

You make a good case - and I believe that the compiler only truly needs the "await" keyword. I don't think that it cares about the "async" modifier.

However, the fact that the compiler insists that you mark your methods with the "async" modifier is a good thing - it allows you to check whether a method will run synchronously/asynchronously without having to dig into its implementation. It also tells you whether you'll have to/be able to await it. I believe these were the reasons to enforce the use of both keywords, and not just one.

A similar case would be: why do you need to mark interface implementation methods with the "public" access modifier, even though it cannot be private/internal/protected/protected internal? Seems redundant right? It does, however, increase readability.

dcastro
  • 66,540
  • 21
  • 145
  • 155
  • 1
    It was my impression it was added to prevent breaking existing code... you could get into all kinds of problems if you had a variable called await! – Liath Jan 28 '14 at 08:18
  • @dcastro, Agreed, the reasons you mentioned make sense and those restrictions provide value. What I really (more often that not) want though, is a simple directive as explained above. i.e. I don't even care if the called method is async/async... I'm telling the compiler to run it on another worker thread no matter what, then hook up the same calling GUI thread again afterwards. Is there really no simple way to do this? – Fortmann Jan 28 '14 at 08:22
  • 1
    @Fortmann The simplest way is `await Task.Run(...);`, which isn't much more complicated. Also, note that the `await` keyword doesn't necessarily mean that the method being called **will** run on another thread. If the `Task` returned by the method has already been completed, then `await` will simply unwrap the result (if any) and carry on, without pausing the current thread. For example, the method might return `Task.FromResult("hi");` – dcastro Jan 28 '14 at 08:31
  • Thanks - I missed that. Something like 'statusStripLabel.Text = await Task.Run(() => { return LongRunningTaskThatReturnsString(); });' looks like the simplest solution. – Fortmann Jan 28 '14 at 08:46
  • I disagree with the last paragraph. Putting the same word in front of each interface member can't improve anything. – Display Name Jun 18 '14 at 17:49
  • @SargeBorsch In this case, it really does improve readability. Imagine you didn't have to mark interface implementations as `public`. You implement two methods A and B as such: `void A(){} void B(){}`. `A` is an interface implementation, `B` is not, it's just a standard method. They look like they have the same access level, but they don't - `A` is public, `B` is private. I'd much rather have the compiler force me to mark `A` as public to avoid confusing the reader. – dcastro Jun 18 '14 at 17:55
  • @Liath is correct. The async keyword was added to be able to introduce the await keyword without breaking existing code. I believe this is explained in Jon Skeet's *C# in Depth*. – Noel Widmer Dec 21 '17 at 14:40
2

I have a blog post on the subject. The primary reason is for backwards compatibility (allowing the await keyword to be a contextual keyword). It's also nice to have the async keyword as an indicator when you read the code.

Also see Eric Lippert's blog post on the subject, as well as the comments on another post of his, Channel9, MSDN forums, and right here on SO.

Community
  • 1
  • 1
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Your blog is good, and explains why some of the alternatives won't work (esp. inference of await and async). More specifically though, I agree with Adelphus' reasoning from SO topic - I would prefer a single simplified directive *in addition* (alternative) to async/await. Knowing exactly what it will do and what the implications are (see second code block of my question). – Fortmann Jan 28 '14 at 16:52
  • `async` does not imply threading. At all. If you want to do work on a background thread and then sync back to the UI thread, then just do `await Task.Run`. The `Task.Run` does work on a background thread, and the `await` allows the UI thread to (asynchronously) wait for that work. – Stephen Cleary Jan 28 '14 at 16:56
  • 1
    @Fortmann: You may find my [`async` intro](http://blog.stephencleary.com/2012/02/async-and-await.html) helpful. – Stephen Cleary Jan 28 '14 at 17:04
  • Thanks, your blog is great - and yes, I understand how async is working (now). A problem is (1) that async is unintuitive and (2) would like another directive in addition to async/await as described above. 'asynchronous' (by definition) means 'not at the same time', and in the historical context of software development it definitely implies concurrent processing - that is why it is unintuitive and misleading to most people. – Fortmann Jan 29 '14 at 07:51
  • @Fortmann: `async` is concurrent. However, it is not *parallel*. That's where the distinction lies. Parallel processing is one form of concurrency, and asynchronous processing is another form of concurrency. I agree that the term "asynchronous" has been (mis)used for years to mean "runs on a background thread" (e.g., the MSDN docs on "asynchronous delegates"), but there aren't many other terms that would fit... – Stephen Cleary Jan 29 '14 at 13:16
  • What is most confusing is that when 'await' is processed, the *same* thread (I've checked this by monitoring the thread IDs) continues running. ONLY once another thread is actually prompted (i.e. Task.Run...) within the target async code, THEN does the calling thread return, etc. – Fortmann Jan 29 '14 at 13:37
  • @Fortmann: It's actually thread-agnostic. (This is covered in my async intro). If you do an `await` on the UI thread, then the default behavior is to capture the UI context and resume the method on that context. You can override this by specifying `ConfigureAwait(false)`. Also, if the method is running on a thread pool thread, there's no context to capture so it (probably) won't resume on the same thread. – Stephen Cleary Jan 29 '14 at 14:01
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/46352/discussion-between-fortmann-and-stephen-cleary) – Fortmann Jan 29 '14 at 15:02
1

Specifying the async keyword is an immediate hint that this method's internals will have to be rewritten from scratch to support the async/await model. As you are probably aware, the generated IL looks little like your original method. You could probably argue that this rewriting might also be implicit, but in a way it makes sense to explicitly allow this to happen.

On the other hand, you have a valid point: something similar happens when implicitly implementing IEnumerable using the yield keyword, which also slightly resembles the async model in that it also creates a state machine. And there doesn't exist a requirement to put a, say, enumerated keyword before such method.

vgru
  • 49,838
  • 16
  • 120
  • 201
  • My understanding was lacking: await by itself has nothing to do with assigning a worker thread. The async keyword is slightly misleading to me because it does not guarantee asynchronous operation (the developer of the async method is responsible for this). If interpreted correctly I understand why the explicit marking is good, i.e. marking something explicitly is good because it provides more information to the caller – Fortmann Jan 28 '14 at 09:33