WPFメモ　非同期キャンセル処理 画面サンプル

# <i class="fa fa-cubes" style="font-size:1em;"></i> 概要
画面で時間のかかる処理を非同期で実行、キャンセルするサンプル。

# <i class="fa fa-cubes" style="font-size:1em;"></i> 画面イメージ
「開始」ボタンで「探索パス」のファイル一覧をリストアップ。
左右の違いはキャンセル処理の実装。
左側はフラグ、右側はトークンを使用。
![async_await01.png](https://qiita-image-store.s3.amazonaws.com/0/142879/73241aaa-c43c-0a0f-9a4d-096743bcdc15.png)

# <i class="fa fa-file-text-o" style="font-size:1em;"></i> コード

```Xml:MainWindow.xaml
<Window x:Class="WpfAsyncAwaitCancelSample.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:WpfAsyncAwaitCancelSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" MinWidth="500" ResizeMode="CanResizeWithGrip">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <StackPanel Grid.ColumnSpan="2" Orientation="Horizontal">
            <Label Content="探索パス"/>
            <TextBox x:Name="searchPath" Text="C:\Work" Width="400" VerticalAlignment="Center"/>
        </StackPanel>
        
        <!--#region 左側-->
        
        <!-- ボタン -->
        <StackPanel Grid.Row="1" Orientation="Horizontal">
            <Button x:Name="startButtonL" Content="開始" Width="100" Height="30" Margin="5" Click="startButtonL_Click"/>
            <Button x:Name="cancelButtonL" Content="キャンセル" Width="100" Height="30" Margin="5" Click="cancelButtonL_Click" />
        </StackPanel>

        <!-- ステータス表示 -->
        <TextBlock Grid.Row="2" x:Name="statusLabelL" Text="-" Margin="5"/>
        
        <!-- ファイル一覧 -->
        <ListBox Grid.Row="3" x:Name="fileListL"/>

        <!--#endregion -->
        
        <!--#region 右側-->
        
        <!-- ボタン -->
        <StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal">
            <Button x:Name="startButtonR" Content="開始" Width="100" Height="30" Margin="5" Click="startButtonR_Click"/>
            <Button x:Name="cancelButtonR" Content="キャンセル" Width="100" Height="30" Margin="5" Click="cancelButtonR_Click" />
        </StackPanel>

        <!-- ステータス表示 -->
        <StackPanel Grid.Row="2" Grid.Column="1">
            <TextBlock x:Name="statusLabelR1" Text="-" Margin="5,5,5,0"/>
            <TextBlock x:Name="statusLabelR2" Text="-" Margin="5"/>
        </StackPanel>

        <!-- ファイル一覧 -->
        <ListBox Grid.Row="3" Grid.Column="1" x:Name="fileListR"/>

        <!--#endregion-->
    </Grid>
</Window>
```

```MainWindow.xaml.cs
using System.IO;
using System.Threading.Tasks;
using System.Windows;

namespace WpfAsyncAwaitCancelSample
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
        }

        #region 左側の処理（キャンセル押してから終了するまでにタイムラグあり）

        /// <summary>
        /// キャンセルフラグ
        /// </summary>
        private bool isCanceled = false;

        /// <summary>
        /// 左の開始ボタン処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void startButtonL_Click(object sender, RoutedEventArgs e)
        {
            // ボタン無効化
            startButtonL.IsEnabled = false;
            // キャンセルフラグ初期化
            isCanceled = false;
            // ステータス表示更新
            statusLabelL.Text = "処理中...";

            // リストボックスクリア
            fileListL.Items.Clear();

            // 時間のかかる処理呼び出し
            bool complete = await TimeConsumingProcessLAsync();

            // ステータス表示更新
            statusLabelL.Text = complete ? "処理完了しました。" : "処理を中断しました。";

            // ボタン有効化
            startButtonL.IsEnabled = true;
        }

        /// <summary>
        /// 左の時間のかかる処理
        /// </summary>
        /// <returns>処理を途中でキャンセルされたらfalse</returns>
        private async Task<bool> TimeConsumingProcessLAsync()
        {
            var files = Directory.EnumerateFiles(
                searchPath.Text, "*", SearchOption.AllDirectories);

            foreach (string item in files)
            {
                if (isCanceled)
                {
                    // キャンセルされたら終了
                    return false;
                }

                // リストボックスにファイルパス追加
                fileListL.Items.Add(item);

                // （動作確認のため）1500ミリ秒待つ
                await Task.Delay(1500);
            }

            return true;
        }

        /// <summary>
        /// 左のキャンセルボタン処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cancelButtonL_Click(object sender, RoutedEventArgs e)
        {
            // キャンセルフラグ設定
            isCanceled = true;
        }

        #endregion

        #region 右側の処理

        /// <summary>
        /// キャンセル用トークンソース
        /// </summary>
        private System.Threading.CancellationTokenSource cts = null;

        /// <summary>
        /// 右の開始ボタン処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void startButtonR_Click(object sender, RoutedEventArgs e)
        {
            // ボタン無効化
            startButtonR.IsEnabled = false;
            // ステータス表示更新
            statusLabelR1.Text = "処理中...";
            statusLabelR2.Text = "-";

            // リストボックスクリア
            fileListR.Items.Clear();

            // キャンセル用トークンソース生成
            cts = new System.Threading.CancellationTokenSource();

            // 時間のかかる処理呼び出し
            bool complete = await TimeConsumingProcessRAsync(cts.Token);

            // ステータス表示更新
            statusLabelR1.Text = complete ? "処理完了しました。" : "処理を中断しました。";

            // ボタン有効化
            startButtonR.IsEnabled = true;
        }

        /// <summary>
        /// 右の時間のかかる処理
        /// </summary>
        /// <param name="token">キャンセル用トークン</param>
        /// <returns>処理を途中でキャンセルされたらfalse</returns>
        private async Task<bool> TimeConsumingProcessRAsync(System.Threading.CancellationToken token)
        {
            var files = Directory.EnumerateFiles(
                searchPath.Text, "*", SearchOption.AllDirectories);

            foreach (string item in files)
            {
                try
                {
                    // リストボックスにファイルパス追加
                    fileListR.Items.Add(item);

                    // （動作確認のため）1500ミリ秒待つ
                    await Task.Delay(1500, token);

                }
                catch (System.OperationCanceledException ex)
                {
                    // キャンセルされた

                    // キャンセル用トークンソース解放
                    cts?.Dispose();
                    cts = null;
                    // ステータス表示更新
                    statusLabelR2.Text = $"例外、OperationCanceledException:{ex.Message}";

                    // 終了
                    return false;
                }
                catch (System.Exception ex)
                {
                    // 例外発生
                    MessageBox.Show(ex.Message);
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 右のキャンセルボタン処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cancelButtonR_Click(object sender, RoutedEventArgs e)
        {
            // まだキャンセル要求されていない？
            if (cts?.IsCancellationRequested == false)
            {
                // キャンセル要求
                cts.Cancel();
            }
        }

        #endregion

    }
}
```

# <i class="fa fa-cubes" style="font-size:1em;"></i> 環境
* Windows 10
* Visual Studio Community 2015 Update 3