10

I'm struggling to figure out how to use visual studio to debug async code sanely, since it's not breaking where I want it to. For example, in this code:

using System;
using System.Threading.Tasks;

namespace TestNS{
    class Program {
        static void Main(string[] args) {
            Foo().Wait(); // sadly, it breaks here
        }

        static async Task Foo() {
            var foo = 42;
            if (foo == 42) {
                throw new Exception(); // want it to break here
            }
        }
    }
}

How can I get Visual Studio to automatically break where the exception is thrown and not at the top of my program? Or equivalently, without restarting the process, can I jump to that context and see the values of those locals?

I understand the root problem is that the exceptions are technically being handled by the behind the scenes async code, so VS doesn't see them as being unhandled. Surely no one else likes this behavior though, and there's a way to tell VS that exceptions that bubble up to an async call aren't really caught... I've tried turning "just my code", "break when this exception is thrown", etc., on and off, but nothing helps.

I am not looking for a solution that includes editing my code or manually adding breakpoints. Similarly, handing certain exceptions in special ways is also not a real solution. Not all the exceptions I want to break on are from my own code; often it will come from a missing key in a dictionary, or something like that. Unexpected exceptions happen all the time when developing code, and I want to see that program state easily without manually placing breakpoints and/or try/catch code, rerunning the program, manually advancing the program until I happen to get to the function. And, that's all under the assumption I can even get it to trigger again, when I don't know anything about why it happened in the first place. And then do that for every exception that ever might occur during development.

On top of all that, if you're looking at the AggregateException that does come back, there isn't even a nice way to figure out where that exception comes from. You've got to "view details", then expand the exception, expand the inner exception, hover over the stack trace, remember the file and line number, close the details, go to the right place manually, and now put manual breakpoints and go debug manually. Why can't the system use this stack trace to do the right thing?

I'm putting all these details in because I have read 20 other SO posts about similar topics. They don't put all these details in, and people seem to go off happily, so I'm confused what I'm doing wrong. None of this happens when developing non-async code. Are all of the steps I'm doing above necessary? What is the best practice to make writing async code as pleasant as writing non-async code in terms of examining exception context?

Chucky Ellison
  • 470
  • 6
  • 14

2 Answers2

17

you need to activate Common Language Runtime exception using CTRL + ALT + E as in the picture below .

enter image description here

sayah imad
  • 1,507
  • 3
  • 16
  • 24
  • 1
    I only had some of them selected. Selecting all of them seems to be what I want. Hopefully I don't run into a situation where I'm debugging code that throws those kinds of exceptions in normal operation, but this is waaaay better than what I was doing before. Thanks! – Chucky Ellison Mar 22 '19 at 23:28
  • 1
    I think it should be important to explain what ticking these boxes does. According to the [documentation](https://learn.microsoft.com/en-us/visualstudio/debugger/managing-exceptions-with-the-debugger?view=vs-2019), any first chance exception whose type is ticked in the Exception Settings panel will make the debugger break. *First chance* exceptions are these handled through a `Try ... Catch` mechanism. They oppose to *unhandled* exceptions, which are never caught down the stack. – Ama Sep 16 '21 at 11:25
  • 1
    So ticking these boxes works because an `AggregateException` captures the inner exceptions and therefore keeps them as *first chance* exceptions, which by default do not make the debugger to break. – Ama Sep 16 '21 at 11:26
0

You need to enable Common Language Runtime exceptions as @sayah already answered.

That said, you should use async all the way up:

static async Task Main(string[] args) {
    await Foo();
}

This will show you the exceptions, where they occour.

Stephen Cleary wrote a good article on the reasons why you want to use async to top.

Take a look at this example:

internal class Program
{
    //static void Main(string[] args)
    //{
    //    try
    //    {
    //        Foo().Wait();
    //    }
    //    catch (FooException)
    //    {
    //        Console.WriteLine("Yep, its a Foo Exception");
    //    }
    //    catch (AggregateException)
    //    {

    //        Console.WriteLine("Damn it, we got an Aggregate");
    //    }

    //    Console.ReadKey();
    //}

    static async Task Main(string[] args)
    {
        try
        {
            await Foo();
        }
        catch (FooException)
        {
            Console.WriteLine("Yep, its a Foo Exception");
        }
        catch (AggregateException)
        {

            Console.WriteLine("Damn it, we got an Aggregate");
        }

        Console.ReadKey();
    }


    class FooException : Exception
    {
    }

    static async Task<int> Foo()
    {
        await Task.Delay(100);
        var foo = 42;
        if (foo == 42) {
            throw new FooException(); // want it to break here
        }

        return 43;
    }
}

If you exchange the commented Main(), you will see, that you get an AggregateException in the non async method, while you get an FooException in the async method.


Note: If this doesn't work for you, you need to set the language level in project's advanced build settings to C# 7.1 or later, as explained here.

  • Right click your project, and click Properties

Build

  • Go to build settings and click advanced, to set the language version to C# 7.1 or later

Advanced

Christian Gollhardt
  • 16,510
  • 17
  • 74
  • 111
  • I tried this as mentioned above, and the behavior is actually worse. The application breaks, but there is no code at all to show in the debugger. Try unchecking the "Common Language Runtime" in "break when thrown" and you should see the same behavior. The problem is "break when thrown" not async all the way up. – Chucky Ellison Mar 23 '19 at 00:11
  • Yeah just tested, and you are right. Not sure about why... If I recall correct, it worked the last time I tried, but it was a more complex application. Anyway, you should do it the `async`, see my example above @ChuckyEllison – Christian Gollhardt Mar 23 '19 at 00:53