Jonathon Anderson
Jonathon Anderson

Reputation: 1200

Task.Run Exception with non-async lambda

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

Upvotes: 0

Views: 585

Answers (1)

Peter Duniho
Peter Duniho

Reputation: 70652

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.

Upvotes: 2

Related Questions