Asynchronen Programmierung mit async/await in .NET (III): Beispiel Anwendung

Das folgende einfache Beispiel zeigt das GUI-Thread Blocking Verhalten einer Windows Presentation Foundation (WPF) Anwendung beim Download eines Files mittels der .NET WebClient-Klasse. Es zeigt, dass mittels async/await das GUI responsive bleibt. Alternativ dazu steht das Blocking Verhalten einer standard synchronen Variante. Das Beispiel folgt Teil Iund Teil IIeiner Serie aus 3 Teilen.

Async-Sync zur Laufzeit

Das Programm downloadet ein durch eine URL spezifiziertes File entweder synchron, oder asynchron mittels async/await. Es zeigt, dass der UI Thread in der synchronen Variante blockiert, in der asynchronen nicht. Die zweite Workload im Beispiel ist die Errechnung der Faktoriellen n! eines Integers n. Hier wird in der asynchronen Variante ein Worker Thread verwendet womit er gleichzeitig zum Download und UI Verwendung non-blocking Verhalten bringt. In der  synchron Variante läuft die Berechnung im UI Thread und blockiert daher. Der asynchrone Download kann durch die Verwendung eines Cancelation Tokens unterbrochen werden. Das Programm ist WPF-basiert mit XAML.

A11

Prerequisites

Die Async CTP Funktionalität ist aktuell in mehreren Deliverables enthalten. Das Beispiel verwendet den Async CTPv3 mit Visual Studio 2010 – siehe Downloads. Dependency ist auf die Reference DLL AsyncCtpLibrary.dll, welche in der VS Solution in einen Folder Libraries gelegt wurde und sich nach der CTPv3 Installation in "%HOMEPATH%\Documents\Microsoft Visual Studio Async CTP\Samples" befindet.

 

Aufbau-Beschreibung

Eingegebene URLs in eine WPF Textbox wird mittels

 (Uri.IsWellFormedUriString(textBoxURL.Text, UriKind.Absolute))

validiert. n mittels Test auf Int32. Die Faktorielle wird wie folgt berechnet.

 
 public static BigInteger Factorial(int n)
 {
     BigInteger _factorial = 1;
     for (int i = 2; i <= n; i++)
     {
         _factorial *= i;
     }
     //Thread.Sleep(20000);
             return _factorial;
 }
 Der synchrone Download läuft über die WebClient.DownLoadFile Methode:
 WebClient client = new WebClient();
 client.DownloadFile(textBoxURL.Text, destinationFile);
  

Der asynchrone Download Block hat den async Modifier dem Button Click Handler vorangestellt. await am Ende dispatched die Methode WebClient.DownloadFileTaskAsync asynchron.

 
 public async void buttonDownloadAsync_Click
     (object sender, RoutedEventArgs e)
 
 ...
 
 await client.DownloadFileTaskAsync
     (new Uri(URL), destinationFile, cts.Token);

Der Async Download wird in dem einfachen Beispiel mit Return void gestartet, was auch als “fire and forget” bezeichnet wird. Der Download kann über den TPL Cancelation Mechanismus abgebrochen werden. Dazu wird zuerst ein Cancelation Token angefordert:

 cts = new CancellationTokenSource();

und wie im obigen await Statement als Argument übergeben. Für den async Download wird ein Progress Bar eingeblendet und ein Cancel Button mit u.A. enabled:

 progressBar.Visibility = Visibility.Visible;

Das Event und der Delegate zum Update des Progress Bars sind

 client.DownloadProgressChanged +=
     new DownloadProgressChangedEventHandler
         (client_DownloadProgressChanged);

 progressBar.Value = e.ProgressPercentage;
 

Ein Worker Thread zur nicht-blockierenden Factorial Berechnung wird wie folgt mit einem Lambda Ausdruck gestartet:

 BigInteger ret = await TaskEx.Run(() => Factorial(factorNumber));

Downloads

Async Support ist in aktuell 3 verschiedenen Downloads enthalten:
(a) Async CTP: AsyncCtpLibrary.dll als Reference hinzufügen,
(b) VS11 mit .NET 4.5 und
(c) .NET 4.5 CTPv3: ein side-by-side Update von .NET <= 3.5 und in-place Update für 4.0. Achtung: da bei dem in-place Update zu z.B. dem Task Typ keine neuen statischen Members zu System.Threading.Tasks.Task hinzugefügt werden können haben manche Types zwischenzeitlich differierende Namen wie TaskEx. Zum Download:

Download VS11 mit .NET 4.5 CTP Download .NET 4.5 CTP Download Async CTP Version 3(Beispiel in Teil III verwendet Async CTPv3) Task-based Asynchronous Pattern Dokument (Stephen Toub)

Visual Studio 2010 Project Directory

 

Source

 // ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Win32;
using System.Net;
using System.Threading.Tasks;
using System.Numerics;
using System.Threading;


namespace dpe.CTPasyncWPF
{

    public partial class MainWindow : Window
    {
        public string destinationFile;
        public string URL;
        int factorNumber = 20000;
        private CancellationTokenSource cts;

        public MainWindow()
        {
            InitializeComponent();
            textBoxStatus.Text = "Enter Download URL";
            factorialslider.Value = factorNumber;
        }

        public void ValidateURL()
        {
            if (Uri.IsWellFormedUriString(textBoxURL.Text, UriKind.Absolute))
            {
                URL = textBoxURL.Text;

            }

            else
            {
                textBoxStatus.Text = "URL Validation Error";
            }
        }

        public static BigInteger Factorial(int n)
        {

            BigInteger _factorial = 1;
            for (int i = 2; i <= n; i++)
            {
                _factorial *= i;
            }
            //Thread.Sleep(20000);
            return _factorial;
        }


        private void buttonDownloadSynchronous_Click(object sender, RoutedEventArgs e)
        {
            ValidateURL();
            if (destinationFile == null)
            {
                SaveFileDestination();
            }
            else
            {
                WebClient client = new WebClient();
                client.DownloadFile(textBoxURL.Text, destinationFile);

            }

        }

        public async void buttonDownloadAsync_Click(object sender, RoutedEventArgs e)
        {
            ValidateURL();
            if (destinationFile == null)
            {
                SaveFileDestination();
            }
            else
            {
                progressBar.Visibility = Visibility.Visible;
                progressBar.Background = Brushes.Blue;
                downloadLabel.Visibility = Visibility.Visible;
                cancelAsyncDonwload.IsEnabled = IsEnabled;

                cts = new CancellationTokenSource();

                try
                {
                    WebClient client = new WebClient();

                    client.DownloadProgressChanged +=
                        new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
                    textBoxStatus.Text = "DownloadFileTaskAsync Download ";

                    await client.DownloadFileTaskAsync(new Uri(URL), destinationFile, cts.Token);
                    textBoxStatus.Text = "Continuing from await Download";
                }
                catch (OperationCanceledException)
                {
                    textBoxStatus.Text = "Download canceled on Task Parallel Lib Token";
                }
            }

        }

        void client_DownloadCallback(object sender, DownloadDataCompletedEventArgs e)
        {
            textBoxStatus.Text = "Synchronous Download completed";
        }

        private void FormatStatusTextBox()
        {
            textBoxStatus.Text = "\n";
        }

        void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage;
        }

        private void quit_Click(object sender, RoutedEventArgs e)
        {
            Application.Current.Shutdown();
        }


        private async void burnCyclesMultithreaded_Click(object sender, RoutedEventArgs e)
        {

            textBoxStatus.Text = "Running Factorial - Worker Thread"; // UI not redrawn instantaniously  

            BigInteger ret = await TaskEx.Run(() => Factorial(factorNumber));

            textBoxStatus.Text = Convert.ToString(ret);

        }

        private void ofdButton_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDestination();
        }

        private void SaveFileDestination()
        {
            try
            {
                Microsoft.Win32.SaveFileDialog sfd = new Microsoft.Win32.SaveFileDialog();
                Nullable<bool> rtn = sfd.ShowDialog();
                if (!string.IsNullOrEmpty(sfd.SafeFileName))
                {
                    destinationFile = sfd.SafeFileName;
                }
                textBoxStatus.Text = "Destination File " + "\"" + destinationFile + "\"" + " selected";

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void textBoxURL_Keydown(object sender, KeyEventArgs e)
        {
            if (e.Key.Equals(Key.Enter))
            {
                ValidateURL();
                textBoxStatus.Text = "URL Changed to " + URL;
            }

        }

        private void burnCyclesBlocking_Click(object sender, RoutedEventArgs e)
        {
            textBoxStatus.Text = "Running Factorial Blocking";
            FormatStatusTextBox();
            BigInteger ret = Factorial(factorNumber);
            textBoxStatus.Text = (Convert.ToString(ret));
        }

        private void factorialslider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            FactorialNumberSelected.Text = Convert.ToString(factorialslider.Value);
            textBoxStatus.Text = "Factorial Number Changed to " + FactorialNumberSelected.Text;
        }


        private void FactorialNumberSelected_TextChanged(object sender, TextChangedEventArgs e)
        {
            int intresult;
            bool inferInt32 = Int32.TryParse(FactorialNumberSelected.Text, out intresult);
            if (inferInt32)
            {
                factorNumber = Convert.ToInt32(FactorialNumberSelected.Text);
                textBoxStatus.Text = "Changed Factorial Number to " + FactorialNumberSelected.Text;
            }
            else
            {
                textBoxStatus.Text = "Enter an Integer ";
            }
        }

        private void ClearStatus_Click(object sender, RoutedEventArgs e)
        {
            textBoxStatus.Clear();

        }

        private void cancelAsyncDonwload_Click(object sender, RoutedEventArgs e)
        {
            cts.Cancel();
        }

    }

}
 Zurück zu Teil I (synchron/asynchron, async in .NET),

weiter zu Teil II (async/await)