May 3rd, 2019

Async-Async: Consequences for exceptions

As we’ve been learning, the feature known as Async-Async makes asynchronous operations even more asynchronous by pretending that they started before they actually did. The effect of Async-Async is transparent to properly-written applications, but if you have been breaking the rules, you may notice some changes to behavior. Today we’ll look at exceptions.

// Code in italics is wrong.

Task task1 = null;
Task task2 = null;
try
{
    task1 = DoSomethingAsync(arg1);
    task2 = DoSomethingAsync(arg2);
}
catch (ArgumentException ex)
{
    // One of the arguments was invalid.
    return;
}

// Wait for the operations to complete.
await Task.WhenAll(task1, task2);

This code “knows” that the invalid parameter exception is raised as part of initiating the asynchronous operation, so it catches the exception only at that point.

With Async-Async, the call to Do­Something­Async returns a fake IAsync­Operation immediately, before sending the call to the server. If the server returns an error in response to the operation, it’s too late to report that error to the client as the return value of Do­Something­Async. Because, y’know, time machine.

The exception is instead reported to the completion handler for the IAsync­Operation. In the above case, it means that the exception is reported when you await the task, rather than when you create the task.

try
{
    Task task1 = DoSomethingAsync(arg1);
    Task task2 = DoSomethingAsync(arg2);

    // Wait for the operations to complete.
    await Task.WhenAll(task1, task2);
}
catch (ArgumentException ex)
{
    // One of the arguments was invalid.
    return;
}

Again, this is not something that should affect a properly-written program, because you don’t know when the server is going to do its parameter validation. It might do parameter validation before creating the IAsync­Operation, or it might defer doing the parameter validation until later for performance reasons. You need to be prepared for the exception to be generated at either point.

In practice, this is unlikely to be something people stumble across because Windows Runtime objects generally reserve exceptions for fatal errors, so you have no need to try to catch them.

Topics
Other

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

6 comments

Discussion is closed. Login to edit/delete existing comments.

  • Ben Voigt

    Two successive days you’ve claimed that “proper” calling code receiving a System.Tasks.Task (or local equivalent) cannot know anything about the demarcation between synchronous and asynchronous stages of the call.  But I disagree.  If you were, for example, to store a Task into a collection and retrieve it later, the retrieval, although it certainly does return a Task object, is completely and reliably synchronous just as a retrieval operation from a collection containing primitives.

    Is there any documentation describing the rules for when the whole call must be assumed to be asynchronous?

    • Raymond ChenMicrosoft employee Author

      Sure, you can synchronously retrieve the Task from a collection in which you saved it. But we’re not talking about the reference to the Task. We’re talking about the code that running inside the Task. All you know is whether the code has produced a result. You don’t know how much progress it has made toward starting.

  • Ihor Nechyporuk

    But won’t ArgumentException be wrapped in AggregateException if it’s thrown at await Task.WhenAll(task1, task2)?
    So basically catch block won’t help.

    • Raymond ChenMicrosoft employee Author

      Oh, yeah, you’ll have to tweak the exception handling, too.

  • Henrik Andersson

    Of course, in non toy programs, you shouldn’t be catching ArgumentException. That exception is only for when you’ve made a mistake and not a normal exception.

    • cheong00

      Unless you’re on web, or feeding parameters from non-typed configuration file (say, .INI)