5

Here is my code:

private static Stopwatch _stopwatch;

static void PrintException(Exception ex)
{
    Console.WriteLine(_stopwatch.Elapsed);
    Console.WriteLine(ex);
}

static void ThrowException1()
{
    throw new InvalidAsynchronousStateException();
}

static void ThrowException2()
{
    throw new NullReferenceException();
}

static async Task ExecuteTask1()
{
    await Task.Delay(1000);
    ThrowException1();
}

static async Task ExecuteTask2()
{
    await Task.Delay(2000);
    ThrowException2();
}

static async Task Execute()
{
    var t1 = ExecuteTask1();
    var t2 = ExecuteTask2();

    try
    {
        await t2;
    }
    catch (NullReferenceException ex)
    {
        // the NullReferenceException will be captured
        Console.WriteLine("==============");
        PrintException(ex);
    }
}

static void Main(string[] args)
{
    TaskScheduler.UnobservedTaskException += (sender, ev) => PrintException(ev.Exception);
    _stopwatch = Stopwatch.StartNew();

    Execute();

    while (true)
    {
        Thread.Sleep(5000);
        GC.Collect();
    }
}

Actually, I didn't await t1 in Execute method, but it seems it was still executed, since I captured the AggregateException about five seconds later.

Can someone tell me when the t1 was executed? In my case, the exceptions order which printed to the Console is 1. NullReferenceException 2. AggregateException

Jerry Bian
  • 3,998
  • 6
  • 29
  • 54

3 Answers3

15

In the async/await world, tasks are "hot". So, when you call ExecuteTask1, the task returned to you is already being processed. It has already started at that point. You can put a Console.WriteLine at the beginning of ExecuteTask* to see that they do start immediately.

await is only used to (asynchronously) wait for the completion of a task. It does not start tasks.

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

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Does garbage collector also keep track of this? Say you do this in a class could properties already be destructed by GC because it thinks there are no more references? (if you don't return/store the Task anywhere). – Joel Harkes Oct 16 '18 at 13:29
  • 1
    @JoelHarkes: In most cases, tasks are not GC'ed because they're rooted by a callback that has been passed to unmanaged code. – Stephen Cleary Oct 16 '18 at 19:51
  • haha thanks 'im most cases'. alright and thus also variables used in callbacks won't be GC'ed as well. thanks. – Joel Harkes Oct 17 '18 at 07:45
  • 1
    @JoelHarkes: Correct. The only case where a `Task` can be GC'ed before it completes is one where it wouldn't have completed anyway. (Assuming everyone's unmanaged calls correctly pin the memory they need). – Stephen Cleary Oct 17 '18 at 18:04
3

If you do not await a task it will still execute, your current execution context will just not "wati for it".

This means you have no direct control over the task and if something goes wrong "inside the task" the exception will not directly propagate to your execution context as it would do when using await or t1.Wait().

In general exceptions which are thrown inside a task are boxed inside an AggregateException, so you can not do the following:

catch (NullReferenceException ex)

You need to do something like and check e.g. for the inner exception:

catch (AggregateException ex)
{
    if(ex.InnerException is NullReferenceException)
        // handle NRE
    else
        throw; // NOT "throw ex" to keep the stack trace
}
Christoph Fink
  • 22,727
  • 9
  • 68
  • 113
  • Thanks for your reply, but in my case if I just replace `catch (NullReferenceException ex)` with `catch (AggregateException ex)`, the exception would not be captured. – Jerry Bian May 09 '14 at 08:03
  • You can also catch the generic `Exception` and then descide based on the current type... – Christoph Fink May 09 '14 at 08:20
3

The task was executed at the point you called ExecuteTask1 here:

var t1 = ExecuteTask1();

You don't need to await a task for it to execute, it will run anyway... you await the task if you want your code to resume executing only after the task has finished otherwise your code will continue running immediately after the task has started running without waiting for it to finish.

Martin Booth
  • 8,485
  • 31
  • 31