概要
画面で時間のかかる処理を非同期で実行、キャンセルするサンプル。
画面イメージ
「開始」ボタンで「探索パス」のファイル一覧をリストアップ。
左右の違いはキャンセル処理の実装。
左側はフラグ、右側はトークンを使用。
コード
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