The suite of SafeAsync helpers provide a safe, consistent way to manage exceptions when running asynchronous code, including a solution to safely fire-and-forget tasks. The library includes an implementation of ICommand, and a Messaging Centre.
centralise error handling
reduce boilerplate try-catch blocks
solve fire-and-forget async void problems
The core of SafeAsync is the SafeContinueWith extension method for the Task object.
Xamarin Forms' Command and MessagingCenter have been refactored to implement "safe execution".
To prevent application crashes from unhandled exceptions, initialise SafeExecutionHelpers with a default exception handler that logs the exception.
SafeExecutionHelpers.SetDefaultExceptionHandler(
(ex) => Console.WriteLine(ex.Message));SafeCommand and SafeMessagingCentere are refactored to automatically execute in a try-catch block. The methods have been refactored to include an optional Action<Exception> onException argument. Safe Execution applies the following logic:
-
If
onExceptionhas been provided and the type ofExceptionthrown matches the type handled in the providedonException, execute the providedonExceptionhandler. -
Otherwise look for a match in the user-defined
GenericExceptionHandlers. Generic handlers are initialized as follows-
SafeExecutionHelpers.Configure(s => s.GenericExceptionHandlers.Add( (ArgumentException ex) => { //Generic handling of ArgumentException here }));
-
-
If no match is found, execute the
DefaultExceptionHandlerif it has been defined. The default handler is agnostic to the type of exception. If defined, it has the effect of silencing unhandled exceptions. -
Finally, if no handler is found and no default handler is defined, throw a
SafeExecutionHelpersExceptionwith the offending exception as itsInnerException.
For Debugging purposes, one can configure SafeExecutionHelpers to always rethrow exceptions after they have been handled:
#if DEBUG
SafeExecutionHelpers.Configure(s => s.ShouldAlwaysRethrowException = true);
#endif📝Tip: When providing an
onExceptiondelegate, if the developer anticipates several different exception-types, this can be handled by using pattern-matching (available from C# version 9). For example:onException: (Exception ex) => { switch (ex) { //Type matching pattern - C# Version 9 case ArgumentException: // Handle bad argument break; case DivideByZeroException: // Handle divide by zero break; case OverflowException: // Handle when integer is too large to be stored break; default: Console.WriteLine(ex.Message); break; } }
In addition to implementing "Safe Execution", the SafeCommand offers the following useful features:
- If a ViewModel has a bindable
IsBusyproperty, will set this totruewhile executing. When using this feature, the default handling of multiple invokations is as follows. If the command is fired multiple times, and the first invokation has not completed the second invokation will be blocked from executing. An example where this is handy is to avoid unstable behaviour from app users double-tapping. - Optionally, the developer can set the
isBlockingargument to false, in which case every invokation will be executed. - The corresponding View can bind to
IsBusyto show an activity indicator when the Command is running. - SafeCommand has been refactored with overloads to execute Asynchronous code. This removes the code-smell
Command(async () => ExecuteCommandAsync), instead writingSafeCommand(ExecuteCommandAsync). - Whereas Xamarin.Forms.Command begins executing on the Main thread, SafeCommand begins executing immediately on the background thread. This prevents UI-blocking code. If the developer explicity wants the command to run on the UI-thread, he can set the
mustRunOnCurrentSyncContextparameter to true.
📝Tip: When letting SafeCommand manipulate IsBusy, ensure that when you use OneWay binding in the
<RefreshView>.<RefreshView IsRefreshing="{Binding IsBusy, Mode=OneWay}" Command="{Binding LoadItemsCommand}">
This class refactors Xamarin's MessagingCenter with the same features described in SafeCommand above. In addition SafeMessagingCenter has been extended with SubscribeAny and UnsubscribeAny. This lets MessagingCenter subscribe to a specified message that may come from any Sender. This prevents unnecessary code repetition if the same Action should be executed in response to a message sent from different classes.
MyAction.SafeInvoke() and MyTask.SafeContinueWith() will apply the Safe Execution logic when handling exceptions.
SafeFireAndForget will safely fire-and-forget a task (instead of awaiting it), applying the Safe Execution logic when handling exceptions. SafeFireAndForget is adapted from Brandon Minnick's AsyncAwaitBestPractices, which in turn was inspired by John Thiriet's blog post, Removing Async Void.