74

Consider this,

Task task = new Task (async () =>{
    await TaskEx.Delay(1000);
});
task.Start();
task.Wait(); 

The call task.Wait() does not wait for the task completion and the next line is executed immediately, but if I wrap the async lambda expression into a method call, the code works as expected.

private static async Task AwaitableMethod()
{
    await TaskEx.Delay(1000);    
}

then (updated according comment from svick)

await AwaitableMethod(); 
kennyzx
  • 12,845
  • 6
  • 39
  • 83
  • In the `AwaitableMethod` you are actually returning and calling Wait on the task returned from the .Delay() method (I'm assuming it returns a `Task`). In the async lambda you are calling Wait on the `Task task`. But still, I have no explanation. – Mario S Oct 24 '12 at 09:43
  • 2
    You should be very careful about mixing `await` with `Wait()`. In many cases, that can lead to deadlocks. – svick Oct 24 '12 at 17:06
  • @svick found a great [example](http://stackoverflow.com/a/11179035/815938) about mixing `await` with `Wait()` – kennyzx Oct 25 '12 at 01:32

2 Answers2

97

In your lambda example, when you call task.Wait(), you are waiting on the new Task that you constructed, not the delay Task that it returns. To get your desired delay, you would need to also wait on the resulting Task:

Task<Task> task = new Task<Task>(async () => {
    await Task.Delay(1000);
});
task.Start();
task.Wait(); 
task.Result.Wait();

You could avoid constructing a new Task, and just have one Task to deal with instead of two:

Func<Task> task = async () => {
    await TaskEx.Delay(1000);
};
task().Wait();
Joe Daley
  • 45,356
  • 15
  • 65
  • 64
  • 11
    I highly recommend reading [Potential pitfalls to avoid when passing around async lambdas](http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx) and [Task.Run vs Task.Factory.StartNew](http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx). – Andrew Mar 18 '13 at 23:23
  • 1
    If the first await is after lots of processing you may still want the double tasks. Instead of `task.Result.Wait()` you can also do `task.Unwrap().Wait()` (or `Unwrap()` for non-void methods). The new `Task.Run` methods automatically unwrap so you only wait on the expected task. – Nelson Rothermel Sep 09 '14 at 17:56
  • 8
    As a beginner, I have the impression they could have done a better job with the `async` keyword; it is very confusing. – drowa Dec 12 '14 at 00:35
  • Is it possible to start the container task _without_ starting the nested task? – drowa Dec 12 '14 at 04:50
  • @drowa async expects functions to return "Hot" tasks to be returned from functions that are already started. If you await a task that has not been started, and will not be started, the program will never come back from the await. The only way to "not start the nested task" is while in the container function to never call the inner function that creates the nested task. – Scott Chamberlain Mar 19 '15 at 14:18
  • 12
    The potential pitfalls link is broken and is now https://devblogs.microsoft.com/pfxteam/potential-pitfalls-to-avoid-when-passing-around-async-lambdas/ – VoteCoffee Jan 16 '20 at 17:34
8

You need to use TaskEx.RunEx.

It natively supports running async methods on the TaskPool by awaiting the inner task internally. Otherwise you'll run into the issue you're facing, where only the outer task is awaited, which obviously completes immediately, leaving either a task which still needs awaiting, or in your case (and even worse) a void lambda which cannot be awaited.

Alternatively, you can await the task twice, providing you construct your outer task correctly (which currently you are not).

Current code (fixed):

Task task = new Task<Task>(async () =>{
    await TaskEx.Delay(1000);
});

task.Start();
var innerTask = await task;
await innerTask;

Using TaskEx.RunEx:

Task task = TaskEx.RunEx(async () =>{ // Framework awaits your lambda internally.
    await TaskEx.Delay(1000);
});

await task;
Lawrence Wagerfield
  • 6,471
  • 5
  • 42
  • 84
  • Nice explanation, but the code TaskEx.Run does not work, still has the same problem. – kennyzx Oct 24 '12 at 14:20
  • 2
    Argh, sorry! I'm using .NET 4.5... I meant to write TaskEx.RunEx. Compare its signature to TaskEx.Run - you'll see why it's specifically for running async methods. – Lawrence Wagerfield Oct 24 '12 at 15:25