9

The async keyword do cause the CIL to change (even if there's no await inside the method), but it is primarily to allow await to be present.

But I did not expect the following to happen:

static void Main(string[] args)
{
    Task t = Go();
    t.Wait();
}

static async Task Go()
{
    Console.WriteLine(1);
    await AAA(3000);
    Console.WriteLine(2);
}


static  Task<object> AAA(int a) // <--- No `async`
{
    TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
    Task.Delay(a).ContinueWith(b => tcs.SetResult(null));
    return tcs.Task;
}

This print:

1
(wait)
2

But if I change

static  Task<object> AAA(int a) 

to

static async  Task<object> AAA(int a) 

It prints:

1
2
(no wait)

Question

Why don't I see the delay? The TCS is only resolved after three seconds. Meanwhile, the task is not resolved and should be awaited.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • 2
    I think [razor118](http://stackoverflow.com/a/32785865/477420) answer is the best demonstration of the problem - change `static Task AAA(int a)` to `static Task AAA(int a)` (with necessary changes to code) and than add `async`. – Alexei Levenkov Sep 25 '15 at 15:55
  • Yeah I got bitten by the as T. thinking of it now - it's a stupid question. Every async method's result that returns something - that something is wrapped by a Task. So here I'm actually returning `Task` – Royi Namir Sep 25 '15 at 15:59
  • 1
    Stupid or not it your call, but definitely educational (and well written). Both versions of code (async/non-async) look plausible and puzzled me (and I bet enough people based on votes). – Alexei Levenkov Sep 25 '15 at 16:09
  • @AlexeiLevenkov Thanks :-) – Royi Namir Sep 25 '15 at 16:10

3 Answers3

8

Without the async keyword you are returning the TaskCompletionSource's task from AAA and so you wait for it to complete (which will happen after the delay completes).

However, when you add the async keyword , the task returned from the method is the state-machine's task that completes synchronously. That task has inside it (as a result) the TaskCompletionSource's task but that isn't the task you are waiting on.

If you want that method to wait for the TaskCompletionSource's task you can await the inner task of Task<Task>:

await ((Task) await AAA(3000));

Or await the TaskCompletionSource's instead of returning it:

async Task AAA(int a)
{
    TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
    Task.Delay(a).ContinueWith(b => tcs.SetResult(null));
    await tcs.Task;
}

Or even better, just awaiting the Task.Delay itself:

async Task<object> AAA(int a)
{
    await Task.Delay(a);
    return null;
}
i3arnon
  • 113,022
  • 33
  • 324
  • 344
6

Because when you return Task from an async method with a return type of Task<object> what you get it Task<Task> with your task (that you expect to be awaited) inside. That what you should do:

static async Task<object> AAA(int a)
{
  await Task.Delay(a);
  return null;
}

In short, try to avoid mixing async-await and direct task operations in one method.

Sergei Rogovtcev
  • 5,804
  • 2
  • 22
  • 35
2

As Serg mentioned, look at this example in Visual Studio:

        static async Task<int> AAA(int a) // <--- No `async`
        {
             TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
             Task.Delay(a).ContinueWith(b =>
             {
                  tcs.SetResult(1);
             });
             return tcs.Task;
        }

You will get error like this:

Since this is an asynchronous method, the return expression must be of type 'int' rather than 'Task', because your method will run synchronously. That's why it is required to return int, not Task.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
razor118
  • 473
  • 4
  • 16
  • 2
    With respect to your last sentance, the method *will* run wynchronously, and it does require an `int` and not a `Task` be returned, but the fact that the method runs synchronously isn't *why* he has to return an `int`. He needs to return an `int` because all `async` methods wrap the returned value in a `Task`. That this method will run synchronously is true, but not the reason for that error. – Servy Sep 25 '15 at 15:44
  • razor118, That answer would be brilliant as comment - using some strong type instead of `Object` would have made error very obvious. Or you can clean it up by applying @Servy's comment. – Alexei Levenkov Sep 25 '15 at 15:50
  • Serv as I know async keyword is responsible to inform compiler to build state machine. It doesn't wraps return type to Task. Look at the IL code of that private static void Test() { } and private static async void TestAsync(){} – razor118 Sep 25 '15 at 15:50
  • @razor118 `async` doesn't wrap return *type* (that's why it still is `Task`) but it wraps the *return* (that's why you have to return `int`) – Sergei Rogovtcev Sep 25 '15 at 15:53