0

I am querying a database and wrapping the results into a view model. Testing an intentionally erroneous connection string, an exception is thrown when the query executes at for (object result in query). According to my research (below) an exception should be handled by an external try/catch block without adding async as a keyword for the lambda. However, when I run the code without the async keyword, the exception is not caught and the program crashes.

Why is my exception not being handled?

Note that the only change here is the addition of async to the lambda expression for Task.Run.


According to Stephen Cleary's comment on this answer an external try/catch should catch an exception without async.

This echoes the first link

I have confirmed that "Break When Thrown" is disabled


Update

From a comment, I tried Disabling "Just my Code"

It did allow the exception to be caught, but it still produced unusual behavior.

If you run the non-async example while "Just My Code" is disabled, the exception is thrown 15 times before returning to the catch block.


Exception not caught by external try/catch

using System;
using System.Linq;
using System.Threading.Tasks;
using System.Data.Linq;
using System.Data.Linq.Mapping;

namespace ThisQuestion
{
    class Program
    {
        static void Main(string[] args)
        {
            DoWork();

            Console.ReadLine();
        }


        private async static void DoWork()
        {
            DataContext _db = new DataContext("badconnectionstring");

            Table<Objects> objects = _db.GetTable<Objects>();

            IQueryable<object> query =
                    from o in objects
                    select o;

            try
            {
                await Task.Run
                (() =>
                {
                    foreach (object result in query) ;
                });
            }
            catch (System.Data.SqlClient.SqlException)
            {
                System.Diagnostics.Debug.WriteLine("SQLError");
            }
        }
    }

    [Table(Name="Objects")]
    class Objects
    {
        private string AColumn;
    }
}

Exception caught by external try/catch

using System;
using System.Linq;
using System.Threading.Tasks;
using System.Data.Linq;
using System.Data.Linq.Mapping;

namespace ThisQuestion
{
    class Program
    {
        static void Main(string[] args)
        {
            DoWork();

            Console.ReadLine();
        }


        private async static void DoWork()
        {
            DataContext _db = new DataContext("badconnectionstring");

            Table<Objects> objects = _db.GetTable<Objects>();

            IQueryable<object> query =
                    from o in objects
                    select o;

            try
            {
                await Task.Run
                (async () =>
                {
                    foreach (object result in query) ;
                });
            }
            catch (System.Data.SqlClient.SqlException)
            {
                System.Diagnostics.Debug.WriteLine("SQLError");
            }
        }
    }

    [Table(Name="Objects")]
    class Objects
    {
        private string AColumn;
    }
}

"Just My Code" Disabled enter image description here


"Just My Code" Enabled, w/o async enter image description here


"Just My Code" Enabled, w/ async enter image description here

Jonathon Anderson
  • 1,162
  • 1
  • 8
  • 24
  • 2
    I don't see a clear problem description here, and I definitely do not see a question being asked. If your intent is *go to the linked posts to see the question being asked*, then this is a duplicate of those posts and doesn't belong here. – Ken White Jul 14 '17 at 17:51
  • Nobody can possibly tell you why an incomplete code snippet that isn't runnable behaves in some way that you haven't described. If you provide a *complete* code snippet, that is capable of actually replicating the behavior described, actually describe how it behaves, and how you expect it to behave, then you could begin to get near an answerable question. Sadly, most SO users (at least to the best of my knowledge) don't have mind reading devices, and thus don't know what you want your code to do, what it's doing, or what it is, when you haven't told us any of those things. – Servy Jul 14 '17 at 18:01
  • I'm not sure what more I can tell you. It's an SQL query, and it throws an exception that is not being handled according to all of the implementation recommendations I've seen. – Jonathon Anderson Jul 14 '17 at 18:02
  • @NonSecwitter read http://stackoverflow.com/help/mcve and https://stackoverflow.com/help/how-to-ask. And then edit your question appropriately. – Ofir Winegarten Jul 14 '17 at 18:05
  • no, I mean I'm really not sure what more I can tell you. I described the problem: the exception is not being handled. I described what I'm doing: SQL query inside task.run. I described the expected results: exception is caught. I have provided all of the relevant code that reproduces the problem. I literally don't know what other information I can provide you. – Jonathon Anderson Jul 14 '17 at 18:10
  • @NonSecwitter You have provided a woefully incomplete code snippet, that isn't runnable, or compilable, and that cannot be used to reproduce anything, and you've said that your code doesn't work, but not *how* it doesn't work. You haven't explained what it actually does, and how that differs from what you expect it to do. Given no usable code snippet, and no description of what happens, nobody could possibly tell you why code that we can't see does something that you haven't described. – Servy Jul 14 '17 at 18:15
  • I've updated to try to be more thorough, but I'm not sure why this code is not sufficient. I've seen questions answered with less so that's a little irritating. Short of giving all of my connection, query, and viewmodel code, what else can I provide? – Jonathon Anderson Jul 14 '17 at 18:23
  • So now you've got an entirely adequate description of what your problem is, and what happens when you run your code. Now you just need to provide a code snippet that is capable of reproducing that problem. As it is, the code that you have provided isn't complete enough for anyone to replicate the behavior you've described. – Servy Jul 14 '17 at 18:24
  • @NonSecwitter It's not the quantity of code that's a problem. A minimal complete example will likely end up being *less* code than you've shown. The problem is that the code you've shown is reliant on a whole bunch of code that you haven't provided. Rather than providing all of the querying code, just replace the code that throws an exception with `throw Exception();`, and provide enough of the handling code for us to be able to run the program and see that the exception isn't caught where the resources you've provided say they should be. – Servy Jul 14 '17 at 18:27
  • This question may help [Why can't I catch an exception from async code](https://stackoverflow.com/questions/19865523/why-cant-i-catch-an-exception-from-async-code). Also note Jon Skeet's answer. Something like the code in his answer is what good questions should contain to be a [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve). The code in your question is a snippet, lacking the method header of the method in which it's contained, and the code that calls that method. Sometimes those things matter a lot. – hatchet - done with SOverflow Jul 14 '17 at 18:46
  • That did change the behavior, however, I'm not entirely sure that is handling the exception correctly either. I'll append my question – Jonathon Anderson Jul 14 '17 at 18:51
  • Possible duplicate of [Why can't I catch an exception from async code?](https://stackoverflow.com/questions/19865523/why-cant-i-catch-an-exception-from-async-code) – hatchet - done with SOverflow Jul 14 '17 at 18:58
  • The two code samples you've provided are exactly the same. A textual compare says that they're an exact match, character for character. – Servy Jul 14 '17 at 19:12
  • whoops. sorry... meant to add `async` after I copied it in – Jonathon Anderson Jul 14 '17 at 19:13
  • @hatchet I'm not sure this is a duplicate, or at least I'm not fully satisfied with that answer because the exception behavior is still very strange. Would the debugger pick up 15 exceptions that weren't passed up to the running application to be handled/crash? – Jonathon Anderson Jul 14 '17 at 19:14
  • 1
    When running both snippets I see the error handled in both cases. They aren't behaving differently. – Servy Jul 14 '17 at 19:22
  • pictures added for clarity. Dunno if that helps – Jonathon Anderson Jul 14 '17 at 21:16
  • 1
    So then your original statement that one of the code snippets isn't handling the error is just wrong, because it is, it's just breaking in the debugger because of how you have your debugger configured. If you don't want the debugger to break, you apparently know how to disable that. – Servy Jul 14 '17 at 21:18
  • But when I reconfigure the debugger, the exception still is not handled correctly – Jonathon Anderson Jul 14 '17 at 22:37

1 Answers1

2

the exception still is not handled correctly

I don't think that's a correct statement.

In both versions of the code, the exception is caught by your catch (System.Data.SqlClient.SqlException) statement. How is that not "correct"?

The only reason you see a difference is because of how the debugger behaves. In the second example, the debugger doesn't report the exception because as far as it's concerned, the exception is being handled. It's observed by the Task object that is returned by the async lambda (note the Task.Run(Func<Task>) overload is called in that case).

In the first code example, the Run() method is executing a straight Action lambda. In this scenario, there's no Task object in the thread where the exception occurs; there's just the one in original thread where you called Task.Run(). So as far as the debugger's concerned, the exception is unhandled in that thread where it happened.

So, in the first example, by the debugger's rules, the exception is unhandled. Of course, it's still observed. The Task object returned by Task.Run() encapsulated the exception and you can observe it by awaiting that Task object.

In the second example, by the debugger's rules, the exception is handled. It's observed in the same thread in which it occurred, by the Task object returned by your async lambda. The debugger's fine with that and doesn't notify you.

But in both examples, the basic behavior of the code is the same. Your task throws an exception, and is caught in the original thread by awaiting the Task object returned by Task.Run() (since in either case, the exception is propagated to that Task object, just by different mechanisms).

Note: the above pertains only to the two complete code examples you provided. You have additional discussion in your question about "15 exceptions", etc. which I cannot address because there's no MCVE that reproduces that behavior. But assuming you correctly represented the basic issue in your two complete code examples, the above will apply to the broader scenario you're asking about.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • Thanks. That's insightful. The "15 Exceptions" code was meant to just be a drop in replacement for the `try/catch` block. And actually, you get the same behavior if you run the version with the non-async lambda and disable "Just My Code"... I'll update the question to reflect. – Jonathon Anderson Jul 15 '17 at 20:28
  • Given the different debugger behavior, is one method of calling `Task.Run` more "correct" (for lack of better terminology) than the other? – Jonathon Anderson Jul 15 '17 at 20:36
  • 1
    _"Given the different debugger behavior, is one method of calling Task.Run more "correct""_ -- IMHO, it's a mistake to conflate the debugger behavior with which type of call is better. Ignore the debugger. What matters is whether the lambda needs to use `await`. If it does, then it needs to be `async`. If it doesn't, then it doesn't need to be `async` and shouldn't be. You can disable catching the exception in the debugger if you really need to, but it's generally my preference to leave the break enabled and just hit F5 while debugging to continue from a known exception. – Peter Duniho Jul 15 '17 at 21:11
  • Thanks again. Any thoughts on the "just my code" behavior? – Jonathon Anderson Jul 15 '17 at 21:27
  • _"Any thoughts on the "just my code" behavior?"_ -- sorry, I'm not really sure what you're asking. You can use that as one of the ways to avoid having the debugger break on that exception. The other way is to right-click the exception in the "Exceptions" window and choose "Continue When Unhandled in User Code" (note that this option isn't visible if you've unchecked "Enable Just My Code" in the Debug options)(note also that this option is also controlled by the checkbox "Break when this exception type is user-unhandled" shown in the Exception Helper). – Peter Duniho Jul 15 '17 at 21:49
  • The debugger settings aren't the most intuitive to understand, but they do work. – Peter Duniho Jul 15 '17 at 21:50
  • In the debugger options you can disable "just my code", when you do instead of throwing one exception and being caught in the appropriate block, it throws 15 exceptions before being caught in the appropriate block. – Jonathon Anderson Jul 16 '17 at 01:02
  • _"it throws 15 exceptions before being caught in the appropriate block"_ -- not in the two complete code examples you provided. I can't comment on code examples you haven't provided. – Peter Duniho Jul 16 '17 at 05:34
  • It's the exact same code with a different debugger setting – Jonathon Anderson Jul 16 '17 at 11:05