我正在开发一个应用程序,处理大量文本数据并统计单词出现的频率(参见:源代码词云)。
以下是我的代码的简化核心部分:
- 枚举所有扩展名为*.txt的文件。
- 枚举每个文本文件中的单词。
- 按单词分组并计算出现次数。
- 按出现次数排序。
- 输出前20个。
LINQ的表现很好。转向PLINQ后,性能显著提高。但是...在长时间运行的查询期间取消操作失效了。
似乎OrderBy查询将数据同步回主线程,而Windows消息没有被处理。
在下面的示例中,我根据MSDN How to: Cancel a PLINQ Query中的说明实现了取消操作,但它不起作用 :(
还有其他想法吗?
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();
}
}
}
}
}