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