3

Here is the code:

static async Task Main(string[] args)
{
    var t = new Task(async () => await AsyncTest());
    t.Start();
    t.Wait();
    Console.WriteLine("Main finished");
}

private static async Task AsyncTest()
{
    Thread.Sleep(2000);
    await Task.Delay(2000);
    Console.WriteLine("Method finished");
}

My expectation is that t.Wait() will actually wait for AsyncTest method completion and output will be:

Method finished 
Main finished

In reality output has only Main finished. Wait() is completed right at the moment when you hit await Task.Delay(2000) inside AsyncTest. The same happens if you replace t.Wait() with await t / await Task.WhenAll(t) / Task.WaitAll(t).

The solution is to rewrite method to synchronous implementation or call Wait() directly on AsyncTest(). However, the question is why does it work in such strange way?

P.S. It's a simplified version of code. I was trying to achieve deferred task execution. In reality Task object is created by one part of program and then later is executed by another part after some specific condition.

UPD: Rewriting var t = new Task(async () => await AsyncTest()) to var t = new Task(()=> AsyncTest().Wait()) also fixes the problem. Though I still don't quite understand why Task doesn't work correctly with async/await inside delegate.

Ghost Master
  • 442
  • 4
  • 12
  • 2
    Don't use `var task = new Task(...); task.Start();`, use just `Task.Run` instead. – mtkachenko Jun 04 '19 at 09:53
  • 1
    In my real program Task has deferred execution. Task object is created in one place, and then later after specific condition executed by another part of program. – Ghost Master Jun 04 '19 at 09:57
  • Possible duplicate of [Task.Wait() not waiting for task to finish](https://stackoverflow.com/questions/38241875/task-wait-not-waiting-for-task-to-finish) – IS4 Jun 04 '19 at 10:42
  • @IllidanS4: it doesn't look like, the method correctly returns a task. There's something unexpected about the `Task.Delay` here, if you comment it out (and leave `Thread.Sleep`) it seems to work as expected. – Wiktor Zychla Jun 04 '19 at 10:44
  • 2
    @WiktorZychla The `AsyncTest` returns a task, but the `async () => await AsyncTest()` lambda throws it away because it is an `Action` (`void`) itself. `() => AsyncTest().Wait()` would "fix" it (still would be wrong because of the `Wait()`). – GSerg Jun 04 '19 at 10:46
  • @GSerg: it should not matter. The constructor doesn't even expects tasks. It's the task returned by the constructor the OPs tries to wait for, not the inner lambda. – Wiktor Zychla Jun 04 '19 at 10:49
  • @WiktorZychla Yes, the OP waits for the Task created by the constructor, but that Task is not able to meaningfully await its own lambda argument, so by extension, the OP cannot do that either. You're not supposed to pass `async () =>` to a Task constructor. – GSerg Jun 04 '19 at 10:51
  • @IllidanS4: it may look like a duplicate of [link](https://stackoverflow.com/questions/38241875/task-wait-not-waiting-for-task-to-finish), but that's a bit different. I thought it should be OK as long I don't use async void in method signature. – Ghost Master Jun 04 '19 at 10:53
  • @GSerg: I know that, I also understand what's the issue here. I believe this sentence of yours *You're not supposed to pass async () => to a Task constructor* should be posted an an actual answer. I mean, the only way to achieve what OP wants is to just call the `AsyncTest` when necessary and `Wait` it, without wrapping it inside another `Task`. – Wiktor Zychla Jun 04 '19 at 10:54
  • 2
    `var t1 = new Task(async () => await AsyncTest()); var t2 = t1.Unwrap(); t1.Start(); t2.Wait();` – user4003407 Jun 04 '19 at 10:54
  • @GSerg you're right. This code `var t = Task.Run(async () => await AsyncTest())` uses `AsyncTaskMethodBuilder`, but this one `var t = new Task(async () => await AsyncTest())` uses `AsyncVoidMethodBuilder`. – mtkachenko Jun 04 '19 at 10:55
  • @PetSerAl Yep. Just replace the last `Wait` with `await t2`. – GSerg Jun 04 '19 at 10:58
  • @mtkachenko: that's why I upvoted your very first answer as it was correct. – Wiktor Zychla Jun 04 '19 at 10:58
  • @WiktorZychla It may not be. `Task.Run` starts the task immediately, the OP apparently wants to be able to have a not-yet-running Task that they may `Start()` and await later. – GSerg Jun 04 '19 at 11:00

4 Answers4

10

I still don't quite understand why Task doesn't work correctly with async/await inside delegate.

Because the Task constructor is only used for creating Delegate Tasks - i.e., tasks that represent synchronous code to be run. Since the code is synchronous, your async lambda is being treated as an async void lambda, which means the Task instance won't asynchronously wait for AsyncTest to complete.

More to the point, the Task constructor should never, ever be used in any code, anywhere, for any reason. It has literally zero valid use cases.

A good replacement for Task.Task is Task.Run, which does understand async lambdas.

In my real program Task has deferred execution. Task object is created in one place, and then later after specific condition executed by another part of program.

In that case, use an asynchronous delegate. Specifically, Func<Task>.

static async Task Main(string[] args)
{
    Func<Task> func = AsyncTest;

    // Later, when we're ready to run.
    await func();
    Console.WriteLine("Main finished");
}

private static async Task AsyncTest()
{
    Thread.Sleep(2000);
    await Task.Delay(2000);
    Console.WriteLine("Method finished");
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
1

A quote from @JonSkeet:

Your async method just returns void, which means there's no simple way of anything waiting for it to complete. (You should almost always avoid using async void methods. They're really only available for the sake of subscribing to events.)

So look at this line of your code:

var t = new Task(async () => await AsyncTest());

Look at the signature of Task constructors:

    public Task(Action action);
    public Task(Action action, CancellationToken cancellationToken);       
    public Task(Action action, TaskCreationOptions creationOptions);
    public Task(Action<object> action, object state);
    public Task(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions);
    public Task(Action<object> action, object state, CancellationToken cancellationToken);
    public Task(Action<object> action, object state, TaskCreationOptions creationOptions);
    public Task(Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions);

All of them are Actions and as you know Action has void return type.

static async Task Main(string[] args)
{
    // best way to do it
    await AsyncTest();


    Console.WriteLine("Main finished");
}

private static async Task AsyncTest()
{
    // Don't use thread sleep, await task delay is fine
    // Thread.Sleep(2000);

    await Task.Delay(2000);
    Console.WriteLine("Method finished");
}
Ali Bahrami
  • 5,935
  • 3
  • 34
  • 53
  • 1
    While this answer makes sense, you seem to have copied the explanation from [this Jon's answer](https://stackoverflow.com/a/38241969/941240). – Wiktor Zychla Jun 04 '19 at 10:37
  • @WiktorZychla Yes, the first paragraph is from legend JonSkeet, I have edited the answer lately but left the laptop and forgot to send the changes. – Ali Bahrami Jun 04 '19 at 11:55
-2

check this

public static async Task Main(string[] args)
{
    var t = Task.Factory.StartNew(async () => await AsyncTest());
    //t.Start();
    t.Result.Wait();    
    t.Wait();
    Console.WriteLine("Main finished");
}

private static async Task AsyncTest()
{
    //Thread.Sleep(2000);
    await Task.Delay(2000);
    Console.WriteLine("Method finished");
}

and this link

Raúl
  • 137
  • 5
-4

Don't use var task = new Task(...); task.Start();, use just Task.Run instead.

In 1st case constructor Task(Action action) is used so you actually doesn't await async method actually. If you inspect real C# without asyn-await syntax sugar you will see AsyncVoidMethodBuilder. In case of Task.Run you use Task.Run(Func<Task> func) so you receive "real" tasks.

So if you want to use constructor you should use approach from comments: new Task<Task>.

mtkachenko
  • 5,389
  • 9
  • 38
  • 68