10

The normal behavior for exceptions thrown from async Task methods is to stay dormant until they get observed later, or until the task gets garbage-collected.

I can think of cases where I may want to throw immediately. Here is an example:

public static async Task TestExAsync(string filename)
{
    // the file is missing, but it may be there again
    // when the exception gets observed 5 seconds later,
    // hard to debug

    if (!System.IO.File.Exists(filename))
        throw new System.IO.FileNotFoundException(filename);

    await Task.Delay(1000);
}

public static void Main()
{
    var task = TestExAsync("filename");
    try
    {
        Thread.Sleep(5000); // do other work
        task.Wait(); // wait and observe
    }
    catch (AggregateException ex)
    {
        Console.WriteLine(new { ex.InnerException.Message, task.IsCanceled });
    }
    Console.ReadLine();
}

I could use async void to get around this, which throws immediately:

// disable the "use await" warning
#pragma warning disable 1998
public static async void ThrowNow(Exception ex)
{
    throw ex;
}
#pragma warning restore 1998

public static async Task TestExAsync(string filename)
{
    if (!System.IO.File.Exists(filename))
        ThrowNow(new System.IO.FileNotFoundException(filename));

    await Task.Delay(1000);
}

Now I can handle this exception right on the spot with Dispatcher.UnhandledException or AppDomain.CurrentDomain.UnhandledException, at least to bring it to the user attention immediately.

Is there any other options for this scenario? Is it perhaps a contrived problem?

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486

2 Answers2

6

If you really want to do this, you can use the same approach Jon Skeet used in his reimplementation of LINQ: create a synchronous method that can throw or call the real asynchronous method:

public static Task TestExAsync(string filename)
{
    if (!System.IO.File.Exists(filename))
        throw new System.IO.FileNotFoundException(filename);

    return TestExAsyncImpl(filename);
}

private static async Task TestExAsyncImpl(string filename)
{
    await Task.Delay(1000);
}

Keep in mind that I think that it's normal to assume that a Task returning method doesn't throw directly. For example, you can use Task.WhenAll() to get all exceptions from several operations under normal circumstances, but this approach won't work when the exception is thrown immediately.

Mike Rosoft
  • 628
  • 7
  • 10
svick
  • 236,525
  • 50
  • 385
  • 514
1

I think the normal behavior is appropriate. Your thread depends on the result of the async function to do its next processing, so the exception should be thrown on your thread. Your thread code could then take appropriate measure to recover from the exception. Because you could pass your Tasks around and initiate many Tasks, your recovery code could be in somewhere else where you need to get the Task result than your original called code. If the exception is thrown immediately, it could be thrown outside your recovery code.

The asynch void function throws immediately which makes sense because nothing depends on its result and there is no Task to pass around.

BTW, the point of exception handling is to recover the application state from exceptions, should not catch any exceptions that you cannot recover. When an exception is thrown, your application state may be corrupted, trying to continue working with corrupted applications cause more problems and security vulnerabilities.

Khanh TO
  • 48,509
  • 13
  • 99
  • 115
  • *"... should not catch any exceptions that you cannot recover"* - that's exactly my point with the missing file example. I'd rather let the user terminate the app right there, than in 5 seconds later (or never, if there a bug in my code and the task never gets observed). Otherwise I agree, but I'd like to let this question hang on for a little longer to see what others may think. – noseratio Mar 29 '14 at 01:20
  • @Noseratio: in case the application could recover from the exception, we should not terminate it. That's my point. When other code uses your code, other code could be able to recover from the exception. – Khanh TO Mar 29 '14 at 01:39
  • I think this is the not case with my example. If the other code (which observes the exception in the future) would try to recover, a *different* file may be there in place, instead of the missing one. This may lead to the loss of data. Check Eric Lippert's ["Vexing exceptions"](http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx). – noseratio Mar 29 '14 at 01:43
  • Recovering from an exception doesn't necessarily mean retrying. It can mean anything, and in our case it probably means aborting the operation and indicating the error to the user (such as displaying a dialog, writing the error to the console/log file/registry, and so on). One shouldn't crash the application just because a file has gone missing. – Mike Rosoft Feb 20 '19 at 08:50
  • @Mike Rosoft: That is what i meant by recovering. Another logic that could be in the recovery code is to roll back corrupted state – Khanh TO Feb 23 '19 at 03:07