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.