LoginSignup
5
10

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-03-04

概要

画面で時間のかかる処理を非同期で実行、キャンセルするサンプル。

画面イメージ

「開始」ボタンで「探索パス」のファイル一覧をリストアップ。
左右の違いはキャンセル処理の実装。
左側はフラグ、右側はトークンを使用。
async_await01.png

コード

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

    }
}

環境

  • Windows 10
  • Visual Studio Community 2015 Update 3
5
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
10