Visual Studio | WPF > Taskクラス > Startボタン, Cancelボタンでのタスクの開始、キャンセル処理 v0.1, v0.2

```txt:動作環境
Windows 8.1 Pro (64bit)
Microsoft Visual Studio 2017 Community
Sublime Text 2
```

関連: [Visual Studio | WPF > 非同期処理 > Link: ざっくりマルチスレッド(非同期処理)](https://qiita.com/7of9/items/5f9d900f01d6e6afc703)

### 処理概要

- Startボタン: Taskを開始
- Cancelボタン: Taskをキャンセル
- Start後に設定時間経過後にTaskが終了
- Taskクラスを用いた実装

### 参考

[Task クラス](https://msdn.microsoft.com/ja-jp/library/system.threading.tasks.task(v=vs.110).aspx)
[Task.Wait メソッド (CancellationToken)](https://msdn.microsoft.com/ja-jp/library/dd321315(v=vs.110).aspx)

### code v0.1

```MainWindow.xaml
<Window x:Class="_171220_t1320_taskCancel.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:_171220_t1320_taskCancel"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel>
            <Button Name="B_start" Content="Start" Height="30"
                Click="B_start_Click"/>
            <Button Name="B_cancel" Content="Cancel" Height="30"
                Click="B_cancel_Click"/>
        </StackPanel>
    </Grid>
</Window>
```

```MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
// 以下を追加した
using System.Threading;

namespace _171220_t1320_taskCancel
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private bool bfStop = false;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void B_start_Click(object sender, RoutedEventArgs e)
        {
            bfStop = false;
            Task tsk = Task.Run(() => WaitTask());
        }

        private void WaitTask()
        {
            using (var cts = new CancellationTokenSource())
            {
                Task tsk = Task.Run(() => SubTask(cts));

                try
                {
                    Console.WriteLine("Task wait");
                    tsk.Wait(cts.Token);
                }
                catch (OperationCanceledException exc)
                {
                    Console.WriteLine("Canceled: {0}", exc.Message);
                }
            }
        }

        private void SubTask(CancellationTokenSource cts)
        {
            for(int loop=0; loop<10; loop++)
            {
                Task.Delay(1000).Wait(); // msec
                if (bfStop)
                {
                    cts.Cancel();
                    return;
                }
            }
            Console.WriteLine("Task ended");
        }

        private void B_cancel_Click(object sender, RoutedEventArgs e)
        {
            bfStop = true;
        }
    }
}
```

### 実行例

- Startボタン押下, Cancelボタン押下
- Startボタン押下, Cancelボタン押下
- Startボタン押下, 放置

![qiita.png](https://qiita-image-store.s3.amazonaws.com/0/32870/77bed807-844b-868d-93ec-03c23cec5517.png)


### 備考

- B_start_Click()内でtsk.Wait()するとCancelボタンが使用できない
    - 処理を抜けていないから
- 上記の実装が「標準的か」はもっといろいろ実装してからでないと分からない
    - async, awaitはそのうち勉強する
    - Task tskの宣言位置は変更した方がいいかもしれない
- usingステートメントにはtry, catchをした方がいいかもしれない: [関連](https://qiita.com/7of9/items/f265dd8db30d58c48586)

### code v0.2

[async/awaitについての備忘録](https://qiita.com/mounntainn/items/3f39e0c57412c48508bf) by @mounntainn さん
を参考にB_cancel_Clickの処理を変更してみた。

- bfStop廃止
- B_cancel_Click()にてcts.Cancel()実行

```MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
// 以下を追加した
using System.Threading;

namespace _171220_t1320_taskCancel
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private CancellationTokenSource cts;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void B_start_Click(object sender, RoutedEventArgs e)
        {
            Task tsk = Task.Run(() => WaitTask());
        }

        private void WaitTask()
        {
            cts = new CancellationTokenSource();

            Task tsk = Task.Run(() => SubTask(cts));

            try
            {
                Console.WriteLine("Task wait");
                tsk.Wait(cts.Token);
            }
            catch (OperationCanceledException exc)
            {
                Console.WriteLine("Canceled: {0}", exc.Message);
            }

            cts.Dispose();
        }

        private void SubTask(CancellationTokenSource cts)
        {
            for(int loop=0; loop<10; loop++)
            {
                Task.Delay(1000).Wait(); // msec
                if (cts.IsCancellationRequested)
                {
                    return;
                }
            }
            Console.WriteLine("Task ended");
        }

        private void B_cancel_Click(object sender, RoutedEventArgs e)
        {
            cts.Cancel();
        }
    }
}
```

v0.1と比べてCancel処理が早くなったようだ。

[WPFメモ　非同期キャンセル処理 画面サンプル](https://qiita.com/Kosen-amai/items/20868649f10b119ceb40) by @Kosen-amai さんの記事にもフラグ処理とトークン処理の違いが紹介されている。

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