8

I have probably worked myself into a rather immature confusion. Please refer the code below (console app)

namespace Tasks101
{
    class Program
    {
        static void Main(string[] args)
        {
            Program p = new Program();
            var x = p.Blah();                
        }

        private async Task Blah()
        {
            await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
        }

        private async void ReturnsVoid()
        {
            await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
        }

        private void Nothing()
        {

        }
    }
}

My question is that in Blah() method I don't have any explicit return statement yet when this executes

var x = p.Blah();

the type of x is Task. Again I have no return statement in ReturnsVoid method but that compiles too.

So the questions are

  1. What is returning a Task from the Blah method without my having a return statement there and why is that same thing not returning anything from ReturnsVoid method.
  2. How do I control what gets returned from the Blah method? What if I had two await statements there one after the other?
i3arnon
  • 113,022
  • 33
  • 324
  • 344
Nikhil
  • 3,304
  • 1
  • 25
  • 42
  • 2
    It's compiler magic. The entirety of a method is included when you return a `Task`, it's not just one statement. You should see everything as one cohesive block that you await that awaits other blocks internally. – Jeroen Vannevel Aug 12 '14 at 15:22

2 Answers2

15

The async keyword transforms the method and constructs the returned Task instance. There is nothing returned from the async void method because it returns void; this lack of a Task is one reason why you should avoid async void. async void is not a natural asynchronous method signature; it is only supported so that event handlers may be async.

If you want to return a value, then you should have the method return a Task<T>, e.g., Task<int> BlahAsync(), and then you can just return the value directly, e.g., return 13; The number of awaits in the method has nothing to do with it. When the method executes the actual return (e.g., return 13), the async keyword interprets that as completing the Task<int> that was already constructed.

I have an async intro on my blog that you may find helpful.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • incidentally it was your blog and your articles in MSDN magazine which lead me down this path. I have worked with Tasks a fair amount in recent past but this time around I was confused by the syntax more so than anything else. So are we saying that it is the "async" keyword that modifies the method to either return a Task a T in case of Task or void if the method is declared as returning void? If so then it is quite in contradiction to MSDN docs which say the only thing async does is enable the use of await in the method construct. Thoughts? – Nikhil Aug 13 '14 at 16:45
  • The `async` keyword does also trigger a state machine transformation, very similar to the one used for enumerator blocks (`yield return`). – Stephen Cleary Aug 13 '14 at 17:46
  • Got that thanks. Finally one last doubt. Borrowing again from your example at http://msdn.microsoft.com/en-us/magazine/dn630647.aspx in Figure 3 you have two await statements. So when the control flow breaks its execution at the first and second await what awaitable would it return back to the calling method? One that represents the first await call or the second one? – Nikhil Aug 14 '14 at 04:33
  • The task (awaitable) returned by an `async` method always represents the method as a whole. So it will not complete until the method completes, including all of its `await`s. – Stephen Cleary Aug 14 '14 at 12:09
  • Thanks Stephen. All set now. On related topic I noticed a few problems with the source code in your MSDN article I mentioned above (http://msdn.microsoft.com/en-us/magazine/dn630647.aspx). I have left my comments on your article on what is the problem and how can it be fixed. I have also written a blog post explaining that along with the fixed source code here http://www.nikgupta.net/2014/08/async-mvvm-patterns/ Would love to know your thoughts on it. – Nikhil Aug 20 '14 at 15:04
  • Ok, so I avoid "async void" by using "async Task" and then I need to return early from my function. The compiler won't let me have a return; statement without returning something so what do I return in this case? It's possible to just make a function return a bool and always return the same true or false value, but for a function that doesn't need to return something, but does need to return early with a return statement, is "async void" acceptable? – David Rector Nov 18 '15 at 22:30
  • @DavidRector: If your method is `async Task` then you can just say `return;`. It works just fine. – Stephen Cleary Nov 19 '15 at 01:16
  • @Stephen, I would swear that I got a compiler error about the return statement not returning something. I will double check that since you say it is not necessary. This is one of those things that I wish were more clearly documented. Thanks for the response. – David Rector Nov 23 '15 at 20:39
11
  1. The compiler is generating a Task for you that represents the entire asynchronous operation.
  2. You have 3 options for async methods:
    • async void - This should be avoided in all cases other than event handlers
    • async Task - Here you have no control of the returned task, and it will be completed when the whole operation has ended (or when an exception is thrown) no matter how many awaits you have in it.
    • async Task<T> - This allows to actually return a value but behaves just the same as async Task.
i3arnon
  • 113,022
  • 33
  • 324
  • 344