81

In C# I have the following two simple examples:

[Test]
public void TestWait()
{
    var t = Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Start");
        Task.Delay(5000).Wait();
        Console.WriteLine("Done");
    });
    t.Wait();
    Console.WriteLine("All done");
}

[Test]
public void TestAwait()
{
    var t = Task.Factory.StartNew(async () =>
    {
        Console.WriteLine("Start");
        await Task.Delay(5000);
        Console.WriteLine("Done");
    });
    t.Wait();
    Console.WriteLine("All done");
}

The first example creates a task that prints "Start", waits 5 seconds prints "Done" and then ends the task. I wait for the task to finish and then print "All done". When I run the test it does as expected.

The second test should have the same behavior, except that the waiting inside the Task should be non-blocking due to the use of async and await. But this test just prints "Start" and then immediately "All done" and "Done" is never printed.

I do not know why I get this behavior :S Any help would be appreciated very much :)

svenskmand
  • 813
  • 1
  • 6
  • 5
  • 1
    Task.Delay is non-blocking. I see no reason why you would use the 2nd construct. – Roy Dictus Nov 07 '14 at 10:24
  • 2
    @RoyDictus they both have their own issues. You should never call`Task.Wait()` – Gusdor Nov 07 '14 at 10:30
  • 1
    possible duplicate of [Waiting for async/await inside a task](http://stackoverflow.com/questions/24777253/waiting-for-async-await-inside-a-task) – i3arnon Nov 07 '14 at 10:58
  • 1
    In the Main() method you can't use "await." You have to use Wait() or the old Thread.Sleep(). – Ron Inbar Jul 10 '17 at 11:20

1 Answers1

91

The second test has two nested tasks and you are waiting for the outermost one, to fix this you must use t.Result.Wait(). t.Result gets the inner task.

The second method is roughly equivalent of this:

public void TestAwait()
{
  var t = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Start");
                return Task.Factory.StartNew(() =>
                {
                    Task.Delay(5000).Wait(); Console.WriteLine("Done");
                });
            });
            t.Wait();
            Console.WriteLine("All done");
}

By calling t.Wait() you are waiting for outermost task which returns immediately.


The ultimately 'correct' way to handle this scenario is to forgo using Wait at all and just use await. Wait can cause deadlock issues once you attached a UI to your async code.

    [Test]
    public async Task TestCorrect() //note the return type of Task. This is required to get the async test 'waitable' by the framework
    {
        await Task.Factory.StartNew(async () =>
        {
            Console.WriteLine("Start");
            await Task.Delay(5000);
            Console.WriteLine("Done");
        }).Unwrap(); //Note the call to Unwrap. This automatically attempts to find the most Inner `Task` in the return type.
        Console.WriteLine("All done");
    }

Even better just use Task.Run to kick off your asynchronous operation:

    [TestMethod]
    public async Task TestCorrect()
    {
        await Task.Run(async () => //Task.Run automatically unwraps nested Task types!
        {
            Console.WriteLine("Start");
            await Task.Delay(5000);
            Console.WriteLine("Done");
        });
        Console.WriteLine("All done");
    }
Community
  • 1
  • 1
brz
  • 5,926
  • 1
  • 18
  • 18
  • 4
    Thanks that worked :) By the way is it always okay to use Task.Run instead of Task.Factory.StartNew or are there cases where I would need Task.Factory.StartNew that Task.Run cannot handle? :) – svenskmand Nov 09 '14 at 08:35
  • 2
    Task.Run(someAction); is a convenience wrapper and equivalent to Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); – Daniel Leiszen Jan 23 '21 at 20:00
  • 1
    @DanielLeiszen: `Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);` does not unwrap nested Task types, so it's not strictly equivalent to `Task.Run(someAction);`. – Arkane Apr 19 '23 at 12:45