進捗処理を追加(並列処理と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 にして隠す
}
}
}
Kewl you shuold come up with that. Excellent!