Sunday, June 05, 2016

Simple WPF app using Task and Await with .NET 4.5

Here is a simple example showing how to create background tasks in a WPF app with .NET 4.5 and above. This is so much easier than the old BackgroundWorker approach.

<Window x:Class="TestAsyncTasks.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestAsyncTasks"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
 <Grid Margin="4">
  <Grid.RowDefinitions>
   <RowDefinition Height="Auto"></RowDefinition>
   <RowDefinition Height="*"></RowDefinition>
  </Grid.RowDefinitions>
  <StackPanel Grid.Row="0" Orientation="Horizontal">
   <Button x:Name="ButtonStart" Content="Start" Click="Start_Click" Padding="4"/>
   <Button x:Name="ButtonCancel" Content="Cancel" Click="Cancel_Click" Padding="4"/>
  </StackPanel>
  <TextBox Grid.Row="1" x:Name="Label1"></TextBox>
 </Grid>
</Window>
 
 
 
 
 
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
 
namespace TestAsyncTasks
 {
 /// <summary>
 /// Interaction logic for MainWindow.xaml
 /// </summary>
 public partial class MainWindow : Window
  {
  public MainWindow()
   {
   InitializeComponent();
   }
 
 
  // To allow background task to be cancelled with cts.Cancel()   
  CancellationTokenSource cts;
 
 
  private async void Start_Click(object sender, RoutedEventArgs e)
   {
   // Disable button so it can't be clicked again until finished  
   ButtonStart.IsEnabled = false;
   Label1.Text = "Start\r\n";
 
   // Setup the function to be called with updates     
   var progressUpdate = new Progress<int>(ReportProgressOnUIThread);
 
   // Create a new CancellationTokenSource and optionally set a time 
   // when the task will automatically cancel if it has not already 
   // finished.              
   int AutoCancelAfterMS = 50000;
   cts = new CancellationTokenSource(AutoCancelAfterMS);
 
   // Must be in Try/Catch to trap the OperationCanceledException  
   try
    {
    int loopTo = 10;
    int result = await MyBackgroundTaskAsync(loopTo, progressUpdate, cts.Token);
 
    // This code does not run until MyBackgroundTaskAsync finishes 
    Label1.Text += $"Final result : {result}\r\n";
    }
   catch (OperationCanceledException ex)
    {
    // Do stuff to handle the cancellation exception    
    Label1.Text +=  "CANCELLED\r\n";
    }
   catch (Exception ex)
    {
    //Do stuff to handle other exceptions       
    Label1.Text += $"Exception : {ex.Message}\r\n";
    }
 
   // Reenable Start button           
   ButtonStart.IsEnabled = true;
   Label1.Text += "Finished\r\n";
   }
 
 
  private void Cancel_Click(object sender, RoutedEventArgs e)
   {
   cts.Cancel();
   }
 
 
  async Task<int> MyBackgroundTaskAsync(int DataFromParent, IProgress<int> progress, CancellationToken ct)
   {
   int result = await Task.Run<int>(async () =>
    {
    for (int n=0; n< DataFromParent; n++)
     {
     //You cannot do this because it's running on a non UI thread
     // Label1.Text += $"{n} I don't work!\r\n";
 
     // Throw OperationCanceledException if cts.Cancel() called 
     ct.ThrowIfCancellationRequested();
 
     // Report progress back to UI thread      
     progress.Report(n);
 
     // Do the slow things in the background     
     await SlowStuff();
     }
 
    // result gets this value which is returned as final result 
    return 42;
    }, ct);
 
   return result;
   }
 
 
  async Task SlowStuff()
   {
   // Simulate some slow code. This runs on a background thread.  
   await Task.Delay(1000);
   }
 
 
  void ReportProgressOnUIThread (int value)
   {
   // This runs on the UI thread so it can update the WPF controls 
   Label1.Text += value.ToString() + "\r\n";
   }
  }
 }