Wednesday, 1 April 2015

Async Exceptions

In synchronous code exceptions travel up the call stack until they're handled or your application crashes. however not the case with asynchronous calls, lets take a look at the following

and the xaml

<Page
    x:Class="pc.asyncException.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:pc.asyncException"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="100"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="1" Text="Async Exception Example"
                   Style="{ThemeResource HeaderTextBlockStyle}"
                   VerticalAlignment="Center" />
        <StackPanel Grid.Column="1" Grid.Row="1">
            <StackPanel Orientation="Horizontal">
                <TextBox x:Name="Numerator_TXT" Header="Numerator"
                   Text="0"/>
                <TextBlock Text="/" VerticalAlignment="Bottom"
                           FontSize="42" Margin="10 0" />
                <TextBox x:Name="Denominator_TXT" Header="Denominator" 
                         Text="0"/>
                <TextBlock Text="=" VerticalAlignment="Bottom"
                           FontSize="42" Margin="10 0" />
                <TextBox x:Name="Result_TXT" Header="Result" 
                         Text="0"/>
            </StackPanel>
            <Button x:Name="Synchronous_BTN" Content="Synchronous"/>
            <Button x:Name="Asynchronous_BTN" Content="Asynchronous"/>
           
            <Button x:Name="Task1_BTN" Content="Task 1"/>
            <Button x:Name="Task2_BTN" Content="Task 2"/>
           
            <Button x:Name="Void_BTN" Content="Void"/>
            <Button x:Name="AsyncTask_BTN" Content="Async Task"/>
        </StackPanel>
    </Grid>
</Page>


and the codebhind

using System;
using System.Threading.Tasks;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace pc.asyncException
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            this.Synchronous_BTN.Click += Synchronous_BTN_Click;
            this.Asynchronous_BTN.Click += Asynchronous_BTN_Click;
            this.Task1_BTN.Click += Task_BTN_Click;
            this.Task2_BTN.Click += Task2_BTN_Click;
            this.Void_BTN.Click += Void_BTN_Click;
            this.AsyncTask_BTN.Click += AsyncTask_BTN_Click;
        }

        void Synchronous_BTN_Click(object sender, RoutedEventArgs e)
        {
            var n = Convert.ToInt32(this.Numerator_TXT.Text);
            var d = Convert.ToInt32(this.Denominator_TXT.Text);

            try
            {
                this.Result_TXT.Text = (n / d).ToString();
            }
            catch (DivideByZeroException ex)
            {
                this.Result_TXT.Text = "Sync undefined";
            }
        }

        async void Asynchronous_BTN_Click(object sender, RoutedEventArgs e)
        {
            var n = Convert.ToInt32(this.Numerator_TXT.Text);
            var d = Convert.ToInt32(this.Denominator_TXT.Text);

            try
            {
                this.Result_TXT.Text = await Task.Run(() => (n / d).ToString());
            }
            catch (DivideByZeroException ex)
            {
                this.Result_TXT.Text = "Async undefined";
            }
        }

        void Task_BTN_Click(object sender, RoutedEventArgs e)
        {
            var n = Convert.ToInt32(this.Numerator_TXT.Text);
            var d = Convert.ToInt32(this.Denominator_TXT.Text);

            try
            {
                //Silently fails
                Task.Factory.StartNew(async () => await this.Dispatcher
                    .RunAsync(CoreDispatcherPriority.Normal, () =>
                        this.Result_TXT.Text = (n / d).ToString()));
            }
            catch (DivideByZeroException ex)
            {
                this.Result_TXT.Text = "Task 1 undefined";
            }
        }

        void Task2_BTN_Click(object sender, RoutedEventArgs e)
        {
            var n = Convert.ToInt32(this.Numerator_TXT.Text);
            var d = Convert.ToInt32(this.Denominator_TXT.Text);

            try
            {
                this.Result_TXT.Text = Task.Factory.StartNew(() => (n / d).ToString()).Result;
            }
            catch (DivideByZeroException ex)
            {
                this.Result_TXT.Text = "Task 2 undefined";
            }
            catch (AggregateException ex)
            {
                this.Result_TXT.Text = "aggregate undefined";
            }
        }

        #region Call Async Method that return void
        void Void_BTN_Click(object sender, RoutedEventArgs e)
        {
            var n = Convert.ToInt32(this.Numerator_TXT.Text);
            var d = Convert.ToInt32(this.Denominator_TXT.Text);

            try
            {
                //Cant await
                CalculateAsync1(n, d);
            }
            catch (DivideByZeroException ex)
            {
                this.Result_TXT.Text = "Void undefined";
            }
        }

        async void CalculateAsync1(int n, int d)
        {
            this.Result_TXT.Text = (n / d).ToString();
        }
        #endregion

        #region Call Async Function that returns Task
        async void AsyncTask_BTN_Click(object Sender, RoutedEventArgs e)
        {
            var n = Convert.ToInt32(this.Numerator_TXT.Text);
            var d = Convert.ToInt32(this.Denominator_TXT.Text);

            try
            {
                //Can await
                await CalculateAsync2(n, d);
            }
            catch (DivideByZeroException ex)
            {
                this.Result_TXT.Text = "Async Task undefined";
            }
        }

        async Task CalculateAsync2(int n, int d)
        {
            this.Result_TXT.Text = await Task.Factory.StartNew(() => (n / d).ToString());
        }
        #endregion
    }
}



OK now let's look at the different attempts individually, let's start with the regular synchronous approach.

void Synchronous_BTN_Click(object sender, RoutedEventArgs e)
{
    var n = Convert.ToInt32(this.Numerator_TXT.Text);
    var d = Convert.ToInt32(this.Denominator_TXT.Text);

    try
    {
        this.Result_TXT.Text = (n / d).ToString();
    }
    catch (DivideByZeroException ex)
    {
        this.Result_TXT.Text = "Sync undefined";
    }

}

straight forward there's a try catch when we try to divide by zero the exception is caught and the corresponding text is set to the result textbox.

Next let's take a look at utilizing the async and await keywords.

async void Asynchronous_BTN_Click(object sender, RoutedEventArgs e)
{
    var n = Convert.ToInt32(this.Numerator_TXT.Text);
    var d = Convert.ToInt32(this.Denominator_TXT.Text);

    try
    {
        this.Result_TXT.Text = await Task.Run(() => (n / d).ToString());
    }
    catch (DivideByZeroException ex)
    {
        this.Result_TXT.Text = "Async undefined";
    }

}

also works like a charm, the exception happens when the task return via the await key word our exception is caught and just as before the corresponding text is set to the restul textbox.

Thirdly lets look at the isolated task method, that is everything is done in a separate task which updates the UI via the dispatcher, this allows us to update the UI Thread

void Task_BTN_Click(object sender, RoutedEventArgs e)
{
    var n = Convert.ToInt32(this.Numerator_TXT.Text);
    var d = Convert.ToInt32(this.Denominator_TXT.Text);

    try
    {
        Task.Factory.StartNew(async () => await this.Dispatcher
            .RunAsync(CoreDispatcherPriority.Normal, () =>
                this.Result_TXT.Text = (n / d).ToString()));
    }
    catch (DivideByZeroException ex)
    {
        this.Result_TXT.Text = "Task 1 undefined";
    }

}

this is where we fall into problems, the exception fires inside the task, however our code never hears of it so we don't update the result textbox, the app just continues on as if nothing happend.

and now lets look at a better way to utilize the TPL

void Task2_BTN_Click(object sender, RoutedEventArgs e)
{
    var n = Convert.ToInt32(this.Numerator_TXT.Text);
    var d = Convert.ToInt32(this.Denominator_TXT.Text);

    try
    {
        this.Result_TXT.Text = Task.Factory.StartNew(() => (n / d).ToString()).Result;
    }
    catch (DivideByZeroException ex)
    {
        this.Result_TXT.Text = "Task 2 undefined";
    }
    catch(AggregateException ex)
    {
        this.Result_TXT.Text = "aggregate undefined";
    }

}

above we use the task.factory, however this time because we ask for the result and don't update the UI from within our task we catch our exception, but not the one we may have been expecting. instead we catch the aggregate exception and our divide by zero is actually the inner exception; still better then not catching it at all.

Now we could potentially end up with various nested InnerExceptions, luckily there's a flatten function

void Task2_BTN_Click(object sender, RoutedEventArgs e)
{
    var n = Convert.ToInt32(this.Numerator_TXT.Text);
    var d = Convert.ToInt32(this.Denominator_TXT.Text);

    try
    {
        this.Result_TXT.Text = Task.Factory.StartNew(() => (n / d).ToString()).Result;
    }
    catch (DivideByZeroException ex)
    {
        this.Result_TXT.Text = "Task 2 undefined";
    }
    catch (AggregateException ex)
    {
        this.Result_TXT.Text = "aggregate undefined";

        foreach(var Ex in ex.Flatten().InnerExceptions)
        {
            //log the exceptions
        }
    }

}

now lets take a look at calling the an async function that returns void

#region Call Async Method that return void
void Void_BTN_Click(object sender, RoutedEventArgs e)
{
    var n = Convert.ToInt32(this.Numerator_TXT.Text);
    var d = Convert.ToInt32(this.Denominator_TXT.Text);

    try
    {
        //Cant await
        CalculateAsync1(n, d);
    }
    catch (DivideByZeroException ex)
    {
        this.Result_TXT.Text = "Void undefined";
    }
}

async void CalculateAsync1(int n, int d)
{
    this.Result_TXT.Text = (n / d).ToString();
}

#endregion

because our asynchronous void method cannot be awaited its exceptions cannot be caught however they don't just continue silently they crash our application, hence it's probably best that our asynchronous functions return tasks.

now let's look at a asynchronous function that instead returns a task

#region Call Async Function that returns Task
async void AsyncTask_BTN_Click(object Sender, RoutedEventArgs e)
{
    var n = Convert.ToInt32(this.Numerator_TXT.Text);
    var d = Convert.ToInt32(this.Denominator_TXT.Text);

    try
    {
        //Can await
        await CalculateAsync2(n, d);
    }
    catch (DivideByZeroException ex)
    {
        this.Result_TXT.Text = "Async Task undefined";
    }
}

async Task CalculateAsync2(int n, int d)
{
    this.Result_TXT.Text = await Task.Factory.StartNew(() => (n / d).ToString());
}

#endregion

here since our calculateAsync2 function returns a task instead of being a void method the exception can be caught and handled in our event.

one thing to note is that when you fire a task without using the await keyword, or without somehow waiting for the result and an exception occurs inside that task, it happens inside an anonymous task and thus is not observed hence the term unobserved exception, this can be a real drag because let's say that you have an anonymous task that saves data to a web service well your application has no way of knowing that something went wrong potentially creating a poor user experience. Imagine saving thinking everything went great, only to find out next time your run your app that you lost all your work.


Exceptions

When it comes to exception handling, the standard practices apply,
  • In methods and functions throw exceptions, don't try and return -1's or nulls or empty strings, something went wrong let your system know through an exception.
  • Don't bury exceptions, when an exception occurs it contains a lot of good information so when you catch one re throw it using just throw; otherwise you'll strip the stack trace and it'll be harder to figure out where's the source of your error.
  • clean up your data, if an exception occurs clean the mess up.
Now lets take a look at a quick example.

and the xaml

<Page
    x:Class="pc.exceptions.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:pc.exceptions"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="100"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="1" Text="Exception Example"
                   Style="{ThemeResource HeaderTextBlockStyle}"
                   VerticalAlignment="Center"/>
        <StackPanel Grid.Column="1" Grid.Row="1">
            <TextBox x:Name="Denominator_TXT" Text="0" Width="100"
                     HorizontalAlignment="Left"/>
            <Button x:Name="DivideByZero_BTN" Content="1/0 Exception" />
        </StackPanel>
    </Grid>
</Page>

and lets look at the code behind

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace pc.exceptions
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            this.DivideByZero_BTN.Click += DivideByZero_BTN_Click;
        }

        //thrid exception thrown
        void DivideByZero_BTN_Click(object sender, RoutedEventArgs e)
        {
            var d = Convert.ToInt32(Denominator_TXT.Text);

            try
            {
                var x = CalculateTwo(CalculateOne, 9, d);
            }
            catch (Exception ex)
            {
                //this is bad, don't do this
                throw ex;
            }
        }

        //second function to throw exception
        double CalculateTwo(Func<int, int, double> CalculationFunc, int A, int B)
        {
            try
            {
                return CalculationFunc.Invoke(A, B);
            }
            catch (DivideByZeroException ex)
            {
                throw;
            }
        }

        //First Function to throw Exception
        double CalculateOne(int a, int b)
        {
            try
            {
                return a / b;
            }
            catch (DivideByZeroException ex)
            {
                throw;
            }
        }
    }
}

What we have is pretty straight forward, basically we create a divide by zero exception on purpose to trace the errors now when we run our app and cause our exception. lets inspect our exception stack trace at each level

CalculateOne

   at pc.exceptions.MainPage.CalculateOne(Int32 a, Int32 b)

We see the original source of our exception

CalculateTwo

   at pc.exceptions.MainPage.CalculateOne(Int32 a, Int32 b)
   at pc.exceptions.MainPage.CalculateTwo(Func`3 CalculationFunc, Int32 A, Int32 B)

Now we can trace the original source of our exception

DivideByZero_BTN_Click

  at pc.exceptions.MainPage.DivideByZero_BTN_Click(Object sender, RoutedEventArgs e)

we've lost our stack trace history, so by inspecting it at or after this level we can't really tell where the error originated from. however if we change

//thrid exception thrown
void DivideByZero_BTN_Click(object sender, RoutedEventArgs e)
{
    var d = Convert.ToInt32(Denominator_TXT.Text);

    try
    {
        var x = CalculateTwo(CalculateOne, 9, d);
    }
    catch (Exception ex)
    {
        //this is bad, don't do this
        throw ex;
    }

}

to

//thrid exception thrown
void DivideByZero_BTN_Click(object sender, RoutedEventArgs e)
{
    var d = Convert.ToInt32(Denominator_TXT.Text);

    try
    {
        var x = CalculateTwo(CalculateOne, 9, d);
    }
    catch (Exception ex)
    {
        //Much better
        throw;
    }

}

then inspect the stack trace of the exception once it's thrown we see

   at pc.exceptions.MainPage.CalculateOne(Int32 a, Int32 b)
   at pc.exceptions.MainPage.CalculateTwo(Func`3 CalculationFunc, Int32 A, Int32 B)
   at pc.exceptions.MainPage.DivideByZero_BTN_Click(Object sender, RoutedEventArgs e)

much better, this again allows us to trace our tack to see where the error actually originated from.

now you may have noticed that once our application runs out of opportunities to handle our exception our application catches it for the final time in debugger via the unhandled excption event



#pragma checksum "c:\users\administrator\documents\visual studio 2013\Projects\pc.exceptions\pc.exceptions\App.xaml" "{406ea660-64cf-4c82-b6f0-42d48172a799}" "3D2BE5729E141535E26842E969B8DF4A"
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------


namespace pc.exceptions
{
#if !DISABLE_XAML_GENERATED_MAIN
    public static class Program
    {
        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Windows.UI.Xaml.Build.Tasks"," 4.0.0.0")]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        static void Main(string[] args)
        {
            global::Windows.UI.Xaml.Application.Start((p) => new App());
        }
    }
#endif

    partial class App : global::Windows.UI.Xaml.Application
    {
        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Windows.UI.Xaml.Build.Tasks"," 4.0.0.0")]
        private bool _contentLoaded;

        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Windows.UI.Xaml.Build.Tasks"," 4.0.0.0")]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        public void InitializeComponent()
        {
            if (_contentLoaded)
                return;

            _contentLoaded = true;
#if DEBUG && !DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT
            DebugSettings.BindingFailed += (sender, args) =>
            {
                global::System.Diagnostics.Debug.WriteLine(args.Message);
            };
#endif
#if DEBUG && !DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
            UnhandledException += (sender, e) =>
            {
                if (global::System.Diagnostics.Debugger.IsAttached) global::System.Diagnostics.Debugger.Break();
            };
#endif
        }
    }
}

this code will cause our application to break and since the exception is unhanded it will then crash, if you would like to prevent that in you app.xaml.cs you can handle the event.

public App()
{
    this.InitializeComponent();
    this.Suspending += OnSuspending;
    this.UnhandledException += App_UnhandledException;
}

private void App_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    //handle excption
    e.Handled = true;
}


now this will keep your application form terminating... some of the times... this isn't exactly recommended actually it's discouraged, however this would be an excellent place to save data to storage or log exceptions, maybe post the exception to a web-service so that you can debug it at a later date.

There are exceptions that will terminate your application regardless if they're handled and of coarse when there's an exception you can no longer guarantee the state of your application and thus the best course of action may very well be to just shut it down.