for WPF developers
非同期処理で重たい処理をおこなう場合に、 中断したくなるときがあります。 ここでは async/await 演算子による非同期処理を途中でキャンセルする例を紹介します。
サンプルプロジェクトはここからダウンロードできます。
ページ内リンク
まず UI の XAML コードとその実行結果を示します。
<Window x:Class="AsyncSample4.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainView" Height="300" Width="300">
<StackPanel>
<Button Content="Click me!" Command="{Binding ButtonCommand}" />
<TextBlock Text="{Binding Result}" />
<Button Content="Cancel" Command="{Binding CancelCommand}" />
</StackPanel>
</Window>
非同期処理の記述は「async/await による非同期処理 その 1」で示したサンプルプログラムとほぼ同じですが、 一部だけ変更して中断処理を実現しようと思います。
まず、ViewModel のプロパティです。
#region 公開プロパティ
private string result = "まだ実行してないよ。";
/// <summary>
/// 実行結果を取得または設定します。
/// </summary>
public string Result
{
get { return result; }
set { SetProperty(ref result, value); }
}
private bool isBusy;
/// <summary>
/// ビジー状態かどうかを取得または設定します。
/// </summary>
public bool IsBusy
{
get { return isBusy; }
set
{
if (SetProperty(ref isBusy, value))
ButtonCommand.RaiseCanExecuteChanged();
}
}
private DelegateCommand buttonCommand;
/// <summary>
/// ボタンコマンドを取得します。
/// </summary>
public DelegateCommand ButtonCommand
{
get
{
return buttonCommand ?? (buttonCommand = new DelegateCommand(
_ =>
{
System.Console.WriteLine("Thread[{0}] 非同期処理をコールします。", Thread.CurrentThread.ManagedThreadId);
HeavyWorkAsync();
System.Console.WriteLine("Thread[{0}] 非同期処理をコールしました。", Thread.CurrentThread.ManagedThreadId);
},
_ =>
{
return !IsBusy;
}));
}
}
private DelegateCommand cancelCommand;
/// <summary>
/// キャンセルコマンドを取得します。
/// </summary>
public DelegateCommand CancelCommand
{
get
{
return cancelCommand ?? (cancelCommand = new DelegateCommand(
_ =>
{
CancelTokenSource.Cancel();
},
_ =>
{
return CancelTokenSource != null;
}));
}
}
#endregion 公開プロパティ
#region private プロパティ
private CancellationTokenSource cancelTokenSource;
/// <summary>
/// キャンセル用のトークン
/// </summary>
private CancellationTokenSource CancelTokenSource
{
get { return cancelTokenSource; }
set
{
cancelTokenSource = value;
CancelCommand.RaiseCanExecuteChanged();
}
}
#endregion private プロパティ
非同期処理を途中で中断するには、CancellationTokenSource クラスを使用します。 非同期処理のほうで CancellationTokenSource クラスの持つ IsCancellationRequested プロパティを確認し、 これが true のときに処理を中断するようにします。 中断を指令する側は、上記のように Cancel() メソッドをコールするだけとなります。
非同期処理を開始する HeavyWorkAsync() メソッドを含む非同期関連のメソッドは次のようになります。
#region private メソッド
/// <summary>
/// 重たい処理を非同期で実行します。
/// </summary>
private async void HeavyWorkAsync()
{
IsBusy = true;
CancelTokenSource = new CancellationTokenSource();
Result = "只今実行中...";
System.Console.WriteLine("Thread[{0}] 非同期処理を実行します。", Thread.CurrentThread.ManagedThreadId);
await HeavyWorkTask();
// ここで一旦 return される
// 非同期処理が終了したらここから再開する
System.Console.WriteLine("Thread[{0}] 非同期処理を実行しました。", Thread.CurrentThread.ManagedThreadId);
if (CancelTokenSource.IsCancellationRequested)
{
Result = "キャンセルしました。";
}
else
{
Result = "終了しました。";
}
CancelTokenSource = null;
IsBusy = false;
}
/// <summary>
/// 重たい処理を実行するタスクを返します。
/// </summary>
/// <returns>重たい処理を実行するタスク</returns>
private Task HeavyWorkTask()
{
return Task.Run(() =>
{
// 重たい処理を実行
System.Console.WriteLine("Thread[{0}] 重たい処理を実行します。", Thread.CurrentThread.ManagedThreadId);
HeavyWork(CancelTokenSource != null ? CancelTokenSource.Token : new CancellationToken(false));
System.Console.WriteLine("Thread[{0}] 重たい処理を終了します。", Thread.CurrentThread.ManagedThreadId);
});
}
/// <summary>
/// 重たい処理を実行します。
/// </summary>
/// <param name="token">キャンセル処理をおこなうための <code>System.Threading.CancellationToken</code> クラスを指定します。</param>
private void HeavyWork(CancellationToken token)
{
int count = 0;
while (count++ < 100)
{
// キャンセルされたかどうか確認する
if (token.IsCancellationRequested)
{
System.Console.WriteLine("Thread[{0}] 重たい処理を中断します。", Thread.CurrentThread.ManagedThreadId);
return;
}
// 重たい処理
Thread.Sleep(30);
}
}
#endregion private メソッド
実行結果を次の図に示します。
Designed by CSS.Design Sample