単語の出現に関する統計を収集する大量のテキストデータを処理するアプリケーションに取り組んでいます (参照:ソースコード Word Cloud )。
ここで、コードの単純化されたコアが何をしているのかを示します。
- *.txt 拡張子を持つすべてのファイルを列挙します。
- 各テキスト ファイル内の単語を列挙します。
- 単語ごとにグループ化し、出現回数を数えます。
- 発生順に並べ替えます。
- トップ20を出力します。
すべてがLINQでうまくいきました。PLINQ に移行したことで、パフォーマンスが大幅に向上しました。しかし...長時間実行されているクエリ中のキャンセル可能性は失われます。
OrderBy クエリがデータをメイン スレッドに同期しており、Windows メッセージが処理されていないようです。
以下の例では、 MSDN How to: Cancel a PLINQ Query whic not work :(に従って、キャンセルの実装をデモンストレーションしています。
他のアイデアはありますか?
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace PlinqCancelability
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
m_CancellationTokenSource = new CancellationTokenSource();
}
private readonly CancellationTokenSource m_CancellationTokenSource;
private void buttonStart_Click(object sender, EventArgs e)
{
var result = Directory
.EnumerateFiles(@"c:\temp", "*.txt", SearchOption.AllDirectories)
.AsParallel()
.WithCancellation(m_CancellationTokenSource.Token)
.SelectMany(File.ReadLines)
.SelectMany(ReadWords)
.GroupBy(word => word, (word, words) => new Tuple<int, string>(words.Count(), word))
.OrderByDescending(occurrencesWordPair => occurrencesWordPair.Item1)
.Take(20);
try
{
foreach (Tuple<int, string> tuple in result)
{
Console.WriteLine(tuple);
}
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex.Message);
}
}
private void buttonCancel_Click(object sender, EventArgs e)
{
m_CancellationTokenSource.Cancel();
}
private static IEnumerable<string> ReadWords(string line)
{
StringBuilder word = new StringBuilder();
foreach (char ch in line)
{
if (char.IsLetter(ch))
{
word.Append(ch);
}
else
{
if (word.Length != 0) continue;
yield return word.ToString();
word.Clear();
}
}
}
}
}