3
\$\begingroup\$

I am using this pattern to easily handle exceptions from async code in a non-async caller. Is this a reasonable thing to do, or am I missing something? In this relatively simply case, I have a function called "SendCommand" that does nothing more than call ProcessCommand, which is an async function. I'm doing this because SendCommand is exported in a Xamarin Android project so that it can be called from my Main.axml layout file (via a reference from onClick), which, as far as I can tell, cannot call async functions.

[Java.Interop.Export("SendCommand")]
public void SendCommand(View view)
{
   try
   {
      ProcessCommand().ContinueWith((t) =>
      {
         try
         {
            t.Wait();
         }
         catch (Exception ex)
         {
            ShowMessage(ex.Message);
         }
      });
   }
   catch(Exception ex)
   {
      ShowMessage(ex.Message);
   }
}

The odd thing about this is that I'm calling wait on a task I already know is complete just to throw any exceptions that may need yet to be handled (converted into proper exceptions in this stack).

\$\endgroup\$
1

1 Answer 1

3
\$\begingroup\$

You're correct that you don't need Wait to inspect the Exception of a completed Task. You can use the Exception property to look at the exception. You can also use the only on faulted continuation option if you only want to perform the action if the task faults. This lets you turn the continuation into:

ProcessCommand()
    .ContinueWith(t => ShowMessage(t.Exception.Message),
        TaskContinuationOptions.OnlyOnFaulted);

Next, you handle the case where ProcessCommand throws, rather than returning a faulted Task. If at all possible, I'd avoid the situation entirely. If it's your own method, redesign it such that it only ever returns a faulted task if there's an exceptional problem, rather than throwing, rather than handling both cases any time you ever want to add a continuation to any asynchronous operation. If you're calling method you don't control, like an API that's constructed to both throw and return faulted tasks, then yes, you do need to handle both, but this should be rare. If you're writing code like this often, then you're probably doing something wrong. If it is something you find yourself doing a lot, then at a minimum write a method that does the boilerplate, rather than repeating it:

public static Task WrapExceptions(Func<Task> asyncFunction)
{
    try
    {
        return asyncFunction();
    }
    catch (Exception e)
    {
        return Task.FromException(e);
    }
}
public static Task<T> WrapExceptions<T>(Func<Task<T>> asyncFunction)
{
    try
    {
        return asyncFunction();
    }
    catch (Exception e)
    {
        return Task.FromException<T>(e);
    }
}

Now you can write WrapExceptions(ProcessCommand) (If ProcessCommand cannot be refactored itself) and have a method that never throws, and only ever returns a faulted task in exceptional situations.

\$\endgroup\$
3
  • \$\begingroup\$ Ignoring the double-exception handling, would you say that it's bad style to use Wait, and your suggested method would be preferred, or is Wait a reasonable way to bring the results of the task back into the synchronous context? \$\endgroup\$
    – BlueMonkMN
    Commented Dec 27, 2017 at 2:37
  • \$\begingroup\$ @BlueMonkMN It's a bad idea to explicitly throw an exception only to immediately catch it, when you have the exception already. Throwing and re-catching an exception for no reason whatsoever is wasting time and resources for no gain. \$\endgroup\$
    – Servy
    Commented Dec 27, 2017 at 14:14
  • \$\begingroup\$ I still see one minor drawback. With the catch block you can handle exception and canceled cases in one block because a canceled exception is thrown, whereas using the method this answer described requires separately checking for and responding to exception versus canceled cases. They might often be separate anyway, but I'm not sure that will always be the case. \$\endgroup\$
    – BlueMonkMN
    Commented Dec 27, 2017 at 14:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.