16

In Steven Toub's article:

To make it easier for developers to write asynchronous code based on Tasks, .NET 4.5 changes the default exception behavior for unobserved exceptions. While unobserved exceptions will still cause the UnobservedTaskException event to be raised (not doing so would be a breaking change), the process will not crash by default. Rather, the exception will end up getting eaten after the event is raised, regardless of whether an event handler observes the exception.

But the result of my experiment does not match the above statement. Below is my code:

static void Main(string[] args)
{
    DownloadAsync("http://an.invalid.url.com");
}

async static void DownloadAsync(string url)
{
    using (var client = new System.Net.Http.HttpClient())
    {
        string text = await client.GetStringAsync(url);
        Console.WriteLine("Downloaded {0} chars", text.Length);
    }
}

Since I pass an invalid url to DownloadAsync() method, the call to HttpClient's GetStringAsync() method will throw an expcetion, and it crashes the application.

So my question is: Does unobserved exceptions in .NET 4.5 still crash app by default?

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
Michael Tsai
  • 661
  • 7
  • 17
  • 5
    It's not unobserved, accessing the text.Length is an implicit wait. – Jesse Apr 18 '13 at 22:03
  • 3
    @Jesse Isn't it the assignment of the result to `text` that causes the result to be observed, rather than the later use of `text.Length`? – Matthew Watson Apr 18 '13 at 22:20
  • 2
    @MatthewWatson you are totally right, in my mind it was a task but of course it's not. – Jesse Apr 18 '13 at 22:22
  • Observing exceptions can be done explicitly (read Task.Exception property) or implicitly (use await or one of the blocking methods). More infos: https://github.com/jbe2277/waf/wiki/Unobserved-Exceptions – jbe Oct 17 '15 at 20:13

1 Answers1

39

You do have a Task with an exception (the one returned by GetStringAsync). However, the await is observing the Task exception, which then propagates out of the DownloadAsync method (which is async void).

Exceptions propagating out of async void methods behave differently; they are raised on the SynchronizationContext that was active when the async void method started (in this case, a thread pool SynchronizationContext). This is not considered an unobserved exception.

If you change DownloadAsync to return Task, then you will have an actual unobserved Task exception, which will be ignored (correctly):

static void Main(string[] args)
{
  DownloadAsync("http://an.invalid.url.com");
  Console.ReadKey();
}

async static Task DownloadAsync(string url)
{
  using (var client = new System.Net.Http.HttpClient())
  {
    string text = await client.GetStringAsync(url);
    Console.WriteLine("Downloaded {0} chars", text.Length);
  }
}
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    @StephenCleary : Very informative! This is not something you come across every day so I'm curious as to where you came across such in-depth knowledge of this particular mechanism. Care to share the book or article? – Anthony Sep 18 '13 at 18:25
  • 1
    @Anthony: .NET reflector, lots of questions (mostly on MSDN forums), and years of experimentation. I am, however, currently *writing* a book. :) – Stephen Cleary Sep 18 '13 at 18:39
  • 1
    @StephenCleary did you ever actually complete your book? – Chris Marisic Aug 21 '14 at 19:21
  • 2
    @ChrisMarisic: Yes, I did. It's available from [O'Reilly](http://tinyurl.com/ConcurrencyCookbook) or [Amazon](http://tinyurl.com/ConcurrencyCookbookAmazon) (both affiliate links). – Stephen Cleary Aug 21 '14 at 19:39
  • That `async void` methods are behaving differently from `async Task` ones is an eye-opener. Thank you! – Ivan Danilov Jul 13 '16 at 16:05