Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling exceptions from the synchronous part of async method

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();
}
like image 512
noseratio Avatar asked Nov 24 '22 06:11

noseratio


1 Answers

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();
}
like image 80
John Gibb Avatar answered Dec 26 '22 02:12

John Gibb