Handling exceptions from the synchronous part of a

2020-03-21 10:02发布

问题:

I'm dealing with the situation where the task I start may throw, while still executing synchronously on the initial thread. Something like this, for illustrative purposes:

static async Task TestAsync()
{
    var random = new Random(Environment.TickCount).Next();
    if (random % 2 != 0)
        throw new ApplicationException("1st");

    await Task.Delay(2000);
    Console.WriteLine("after await Task.Delay");
    throw new ApplicationException("2nd");
}

From the calling code, I'd like to be able to catch any exceptions, possibly thrown from the synchronous part (i.e., until await Task.Delay()). Here's how I'm currently doing it:

static void Main(string[] args)
{
    try
    {
        var task = TestAsync();
        if (task.IsFaulted)
            task.GetAwaiter().GetResult();
        Console.WriteLine("TestAsync continues asynchronously...");
    }
    catch (Exception e)
    {
        Console.WriteLine("Error: " + e.ToString());
    }

    Console.WriteLine("Press Enter to exit...");
    Console.ReadLine();
}

This works, although it looks a bit mouthful, as there is no Result on the Task.

I've also tried task.Wait() instead of task.GetAwaiter().GetResult(). That always gives me AggregateException which I have to unwrap (rather than expected ApplicationException directly).

Is there any other options?

[EDITED] To address the comments: I do this, because if the task fails instantly, I don't want to add it to the list of the pending tasks I maintain. The task itself knows nothing about such a list (and it doesn't have to). I still want to log the exception, and make user aware of it. I could also do throw task.Exception, but that wouldn't give the exception stack frame captured with ExceptionDispatchInfo.

[UPDATE] Inspired by other answers and comments: if I have full control over TestAsync and I don't want introducing new class members, I also could do something like below. It might come handy when validating arguments:

static Task TestAsync(int delay)
{
    if (delay < 0)
        throw new ArgumentOutOfRangeException("delay");

    Func<Task> asyncPart = async () =>
    {
        Console.WriteLine("await Task.Delay");
        await Task.Delay(delay);
        throw new ApplicationException("2nd");
    };

    return asyncPart();
}

回答1:

I'd split it into two parts, rather than relying on task.GetAwaiter().GetResult() to work. I'd be afraid that someone maintaining TestAsync could unwittingly break things in the future.

This is how I would write it. This should preserve the behavior you've got, but I find it more obvious what's going on:

static Task Test()
{
    var random = new Random(Environment.TickCount).Next();
    if (random % 2 != 0)
        throw new ApplicationException("1st");

    return TestAsync();
}

static async Task TestAsync()
{
    await Task.Delay(2000);
    Console.WriteLine("after await Task.Delay");
    throw new ApplicationException("2nd");
}



static void Main(string[] args)
{
    try
    {
        Test();
        Console.WriteLine("TestAsync continues asynchronously...");
    }
    catch (Exception e)
    {
        Console.WriteLine("Error: " + e.ToString());
    }

    Console.WriteLine("Press Enter to exit...");
    Console.ReadLine();
}


回答2:

Generally speaking, exceptions should not be used for routine error handling in your application. Throw exceptions for "exceptional" circumstances, where the program cannot continue because something unexpected happened and you need to do a hard stop.

Granted, I do not know precisely what your use case is, but whenever I'm using async tasks, the piece that is going to fail unexpectedly is usually also the piece that should be asynchronous (e.g. connecting to a database).

Regardless, how I would do this is to put your TestAsync method into its own class. Then, you can have a method (or property) bool TestAsync.IsValid to determine whether the task is ready to go and should be queued for execution; then, you can run your async task if the answer is true: TestAsync.RunAsync().