並列処理と UI スレッド

Visual C# 2010 (と.NET Framework 4)でマルチスレッド処理がかなり楽に書けるようになっています 🙂 (参考「連載:C# 4入門 第2回 タスク並列ライブラリ」「連載:C# 4入門 第3回 TaskクラスとPLINQ(Parallel LINQ)」)。

ただ、ウィンドウ・アプリケーションだと、UI スレッドでの画面更新が必要になる制限は以前どおりで、この部分の日本語で書かれた参考ソースが(ちょっと調べた限りでは)まだ少ないです。この部分が書きやすいか、自由度はどうなのかというのに興味があったので、MSDN ライブラリを調べつつ、ちょっと書いてみました。

プログラム作成の前提は次のとおりです。

  • UIはWPFで作成
  • 並列処理させるのは総当りでの素数検索

総当りでの素数検索にしたのは、少ない記述量(何をやってるのか見ればすぐ分かる)でそこそこ重たい処理、という理由です 8)

MainWindow.xaml

<Window x:Class="WpfTaskParallelTest.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="*"/>
        </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" />
        <ScrollViewer Grid.Row="2" 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.Tasks;
using System.Collections.Concurrent;

namespace WpfTaskParallelTest
{
    ///
    /// MainWindow.xaml の相互作用ロジック
    ///
    public partial class MainWindow : Window
    {
        private const int 総当りの開始 = 2;
        private const int 総当りの終了 = 50000;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void btnDoSerach_Click(object sender, RoutedEventArgs e)
        {
            // UI スレッドへのスケジュール用
            var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();

            var task = Task.Factory.StartNew(() =>
            {
                var resultQueue = new ConcurrentQueue();
                Parallel.For(総当りの開始, 総当りの終了, (n) =>
                {
                    Boolean flag = true;
                    for (var i = 総当りの開始; i < n; i++)
                    {
                        if ((n % i) == 0)
                        {   // 割り切れたら素数ではない
                            flag = false;
                            break;
                        }
                    }
                    if (flag)   // 素数だったら resultQueue へ文字として追加
                        resultQueue.Enqueue(n.ToString());
                });
                // resultQueue から取り出して、素数の一覧を作成
                string result = "";
                foreach (var m in resultQueue)
                {
                    result += m + ", ";
                }
                return result;
            });
            // task 実行終了後に、実行結果を UI スレッドにて表示する
            var continueTask = task.ContinueWith((taskRef) =>
            {
                this.tbkResult.Text = taskRef.Result;
            }, UISyncContext);
        }
    }
}

やり方が分かると、記述は簡単で、.NET Framework 2 で登場した BackgroundWorker より自由度が大きいですね。これなら使ってみたいなと思わされます 😉

やっていることは、「Task<string>.Factory.StartNew」で並列処理を開始させて、「Parallel.For」で並列の for ループ(確認する数の生成(そのなかで生成した数が素数であるかを確認する for ループを走らせている))を走らせてます(生成した数を何人かいる小人さんの一人に渡して、渡された小人さんは、その数が素数であるかを各自確認しているということです(確認の途中で素数でないことが判明したら、その時点で break したいので、こういう構成になっています))。
ポイントは次の3つかと。

  • var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();
  • ContinueWith の第二引数に UISyncContext
  • ConcurrentQueue の利用

ConcurrentQueue は、.NET Framework 4 で導入されたスレッドセーフなコレクションです。

小人さんが一人のときには、メモへの書き込みなどに競合は起きませんが、小人さんが二人以上働き始めると競合が起きてしまうので、何がしかのコントロール(今書き込みとかして良い小人さんは誰?)が必要になるということです。

また、素数の判定をマルチスレッドで行っているため、素数一覧は順不同になります(昇順に並んでいるように見えても、きっとどこかで順番が前後しているかと)。

あと、このソースはあくまで感触を確かめるためのものなので、UI の操作(検索中はボタンをクリック出来ないほうがいいよね?とか)やエラー処理(特に並列処理中の割り込み発生など)をまったく書いていません。もし自分のプログラム作成の参考にされる場合には、それらの点を考慮してください。


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

 


4 thoughts on “並列処理と UI スレッド

  1. I think this is among the most important information for me. And i am glad reading your article. But should remark on some general things, The site style is great, the articles is really great : D. Good job, cheers

コメントを残す

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