1

I have a hard time understanding how async/await works in various non-happy-path cases. For example, I have the following code:

    class Program
    {
        static void Main(string[] args)
        {
            Do();
            Console.ReadLine();
        }

        private static void Do()
        {
            TaskScheduler.UnobservedTaskException += (s, e) =>
            {
                Console.WriteLine($"Unobserved Exception : {e.Exception.Message}");
                e.SetObserved();
            };
            
            try
            {                
                ThrowsAsync();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Caught in try/catch  : {ex.Message}");
            }
        }

        private static async Task ThrowsAsync()
        {    
            Console.WriteLine("Throwing");
            throw new Exception("FAILURE");
        }
    }

There are two things that I do not understand:

  1. The ThrowsAsync method is async, however, it does not contain any await. I would assume that in such a case the method would execute like a "normal" synchronous method. However, the exception that it throws is never caught in the catch block.
  2. Trying to somehow catch the exception, I added the handler for TaskScheduler.UnobservedTaskException. However, it is never executed. Why is that?

I know that the exception would be caught if I awaited ThrowsAsync. However, I'm experimenting to get a better understanding of how it works.

I'm running that code using .NET 5 and Linux-based OS.

mnj
  • 2,539
  • 3
  • 29
  • 58
  • https://stackoverflow.com/questions/19865523/why-cant-i-catch-an-exception-from-async-code – urlreader Nov 14 '21 at 18:57
  • 1
    For 2nd one [this](https://stackoverflow.com/questions/3284137/taskscheduler-unobservedtaskexception-event-handler-never-being-triggered) can explain it. For the first one `ThrowsAsync` is async and returns a `Task` so until it is awaited it should not throw. – Guru Stron Nov 14 '21 at 18:57
  • 2
    Also for the first point - [this](https://blog.stephencleary.com/2016/12/eliding-async-await.html#exceptions). – Guru Stron Nov 14 '21 at 18:59
  • @GuruStron Regarding the `UnobservedTaskException`. I tried to follow the recommendation in the linked SO. I added the following at the end of the `Do` method: `Console.WriteLine("GC"); Thread.Sleep(100); GC.Collect(); GC.WaitForPendingFinalizers();`. It didn't change anything. I see the "GC" being printed in the console, that's all. – mnj Nov 14 '21 at 19:15
  • @Loreno are you running in debug or release mode? – Guru Stron Nov 14 '21 at 19:17
  • @GuruStron I just tried with Release - the handler is now being invoked. That's actually not really good if the program executes differently in Debug and Release... – mnj Nov 14 '21 at 19:20
  • @GuruStron As I go through the linked resources, I see that async/await in .NET is pretty complex and behaves differently in various situations. Are you aware of any resource that summarizes all the weirdness in one place? – mnj Nov 14 '21 at 19:21
  • @Loreno that is actually normal. In the debug compiler not only does not apply optimizations but it also generates some stuff not to "lose" variables going out of context for debug experience. – Guru Stron Nov 14 '21 at 19:21
  • 1
    @Loreno you can go hardcore with [this article](https://devblogs.microsoft.com/premier-developer/dissecting-the-async-methods-in-c/) or read blog and book by Stephen Cleary (blog was linked earlier). – Guru Stron Nov 14 '21 at 19:25

1 Answers1

5
  1. As described for example in this blog post by Stephen Cleary - the state machine for async methods will capture exceptions from your code and place them on the returned task, i.e. method invocation will not throw, you will be able to catch exception if await the result.

  2. As for TaskScheduler.UnobservedTaskException - check out this answer and be sure to run code in Release mode.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132