並列処理のキャンセル操作(その2)

進捗処理を追加(並列処理とUIスレッドへの追加)の実行中にキャンセルをするコードがどんな感じになるのか興味があったので、MSDN オンラインを参照しながら試行錯誤してみたのですが、とりあえず実行中に例外が起こらないものができました。ポイントは次のようなところです。

新しいキャンセルモデルが例外通知の仕組みを利用して、タスクの停止を実現しているので、 return するだけの簡単な例を除くと、例外のハンドリングが必要になると思います。

なお、キャンセル通知があったときに return するだけの簡単なコードの例を並列処理のキャンセル操作(その1)で書いています。

それでは以下にコード例を書きます。進捗処理を追加(並列処理とUIスレッドへの追加)からの変更行は強調表示しています。もっと良い方法があるよとか、何か指摘するようなことがあったら、お知らせくださると嬉しいです。

MainWindow.xaml

<Window x:Class="WpfTaskParallelTest003.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" Text="パラレルのテスト・素数検索" FontSize="12pt"
                   HorizontalAlignment="Center"/>
        <Button Grid.Row="1" Name="btnDoSerach" Content="検索実行" Padding="4,0" Margin="4"
                HorizontalAlignment="Center" Click="btnDoSerach_Click" />
        <ProgressBar Grid.Row="2" Name="pgbProgress" Height="20" />
        <Button Grid.Row="3" Name="btnCancel" Content="キャンセル" Padding="4,0" Margin="4"
                HorizontalAlignment="Center" Click="btnCancel_Click" />
        <ScrollViewer Grid.Row="4" VerticalScrollBarVisibility="Auto">
            <TextBlock Name="tbkResult" Margin="4" TextWrapping="Wrap"/>
        </ScrollViewer>
    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace WpfTaskParallelTest003
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private const int 総当りの開始 = 2;
        private const int 総当りの終了 = 50000;
        private static double btnCancelHeight;  // キャンセルボタンの高さを保存しておく
        // キャンセルを通知するためのオブジェクト
        private CancellationTokenSource tokenSource;
        private Task<string> task;

        public MainWindow()
        {
            InitializeComponent();

            btnCancelHeight = this.btnCancel.Height;
            this.btnCancel.Height = 0;
        }

        private void btnDoSerach_Click(object sender, RoutedEventArgs e)
        {
            tokenSource = null;
            tokenSource = new CancellationTokenSource();
            var token = tokenSource.Token;  // トークンを取得
            // UI スレッドへのスケジュール用
            var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();

            // プログレスバー設定
            this.pgbProgress.Minimum = 総当りの開始;
            this.pgbProgress.Maximum = 総当りの終了 - 1;
            this.pgbProgress.Value = 総当りの開始;
            this.pgbProgress.Height = 20;

            this.tbkResult.Text = "";

            this.btnCancel.Height = btnCancelHeight;    // キャンセルボタンの高さを戻して表示させる

            task = Task<string>.Factory.StartNew(() =>
            {
                // キャンセルが要求されていたら、OperationCanceledException を投げる
                token.ThrowIfCancellationRequested();

                var progressValue = 総当りの開始 - 1;     // 進捗報告用
                var resultQueue = new ConcurrentQueue<string>();
                var pOoptions = new ParallelOptions();  // ParallelOptions オブジェクトを作成
                pOoptions.CancellationToken = token;    // ParallelOptions オブジェクトにトークンを設定
                Parallel.For(総当りの開始, 総当りの終了, pOoptions, (n, loopState) =>
                {
                    // キャンセルが要求されているか?
                    if (token.IsCancellationRequested)
                    {
                        loopState.Stop();   // Parallel.For ループを停止
                        return;
                    }

                    Boolean flag = true;
                    for (var i = 総当りの開始; i < n; i++)
                    {
                        if ((n % i) == 0)
                        {   // 割り切れたら素数ではない
                            flag = false;
                            break;
                        }
                    }
                    if (flag)   // 素数だったら resultQueue へ文字として追加
                        resultQueue.Enqueue(n.ToString());

                    // UI スレッドで進捗を報告(UIスレッドにスケジューリングされるので、この部分は入れ子のタスクに切り出す)
                    Task reportProgressTask = Task.Factory.StartNew(() =>
                    {
                        // キャンセルが要求されていたら、OperationCanceledException を投げる
                        token.ThrowIfCancellationRequested();

                        progressValue++;    // 進捗報告を 1 進める
                        this.pgbProgress.Value = progressValue;     // プログレスバーの表示を更新
                    }, token,
                       TaskCreationOptions.None,
                       UISyncContext);

                    // デバッグ実行の場合、例外検知でメッセージが出るので F5 キーで継続するか、
                    // ツール - オプション - デバッグ の 'マイコードのみ' 設定を有効にする のチェックを外して
                    // メッセージ通知を抑制する
                    try
                    {
                        reportProgressTask.Wait();  // reportProgressTask の実行終了を待つ
                    }
                    catch (AggregateException ae)
                    {
                        foreach (var aef in ae.Flatten().InnerExceptions)
                        {
                            if (aef is OperationCanceledException)
                            {
                                return;
                            }
                            else
                            {
                                throw;
                            }
                        }
                    }

                });

                // resultQueue から取り出して、素数の一覧を作成
                string result = "";
                foreach (var m in resultQueue)
                {
                    result += m + ", ";
                }
                return result;
            }, token);

            // task 実行終了後に、実行結果を UI スレッドにて表示する
            var continueTask = task.ContinueWith((taskRef) =>
            {
                this.pgbProgress.Height = 0;    // プログレスバーの高さを 0 にして隠す
                this.btnCancel.Height = 0;      // キャンセルボタンの高さを 0 にして隠す
                this.tbkResult.Text = taskRef.Result;
            }, token, TaskContinuationOptions.NotOnCanceled, UISyncContext);
        }

        private void btnCancel_Click(object sender, RoutedEventArgs e)
        {
            tokenSource.Cancel();   // キャンセルを通知する

            this.pgbProgress.Height = 0;    // プログレスバーの高さを 0 にして隠す
            this.btnCancel.Height = 0;      // キャンセルボタンの高さを 0 にして隠す
        }
    }
}

並列処理と UI スレッドのインデックスへ

 


1 thought on “並列処理のキャンセル操作(その2)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です