WPF
task
myVisualStudioStudy
Asynchronous

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

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

関連: Visual Studio | WPF > 非同期処理 > Link: ざっくりマルチスレッド(非同期処理)

処理概要

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

参考

Task クラス
Task.Wait メソッド (CancellationToken)

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

備考

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

code v0.2

async/awaitについての備忘録 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メモ 非同期キャンセル処理 画面サンプル by @Kosen-amai さんの記事にもフラグ処理とトークン処理の違いが紹介されている。

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