4

単語の出現に関する統計を収集する大量のテキストデータを処理するアプリケーションに取り組んでいます (参照:ソースコード Word Cloud )。

ここで、コードの単純化されたコアが何をしているのかを示します。

  1. *.txt 拡張子を持つすべてのファイルを列挙します。
  2. 各テキスト ファイル内の単語を列挙します。
  3. 単語ごとにグループ化し、出現回数を数えます。
  4. 発生順に並べ替えます。
  5. トップ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();
                }
            }
        }
    }
}
4

3 に答える 3

3

Jon が言ったように、バックグラウンド スレッドで PLINQ 操作を開始する必要があります。このようにして、操作が完了するまで待機している間にユーザー インターフェイスがハングすることはありません (したがって、[キャンセル] ボタンのイベント ハンドラーを呼び出すことができCancel、キャンセル トークンのメソッドが呼び出されます)。PLINQ クエリは、トークンがキャンセルされると自動的にキャンセルされるため、心配する必要はありません。

これを行う 1 つの方法を次に示します。

private void buttonStart_Click(object sender, EventArgs e)
{
  // Starts a task that runs the operation (on background thread)
  // Note: I added 'ToList' so that the result is actually evaluated
  // and all results are stored in an in-memory data structure.
  var task = Task.Factory.StartNew(() =>
    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).ToList(), m_CancellationTokenSource.Token);

  // Specify what happens when the task completes
  // Use 'this.Invoke' to specify that the operation happens on GUI thread
  // (where you can safely access GUI elements of your WinForms app)
  task.ContinueWith(res => {
    this.Invoke(new Action(() => {
      try
      {
        foreach (Tuple<int, string> tuple in res.Result)
        {
          Console.WriteLine(tuple);
        }
      }
      catch (OperationCanceledException ex)
      {
          Console.WriteLine(ex.Message);
      }
    }));
  });
}
于 2011-08-02T18:14:06.957 に答える
1

現在、UI スレッドでクエリ結果を繰り返し処理しています。クエリは並行して実行されていますが、UI スレッドで結果を繰り返し処理しています。これは、UI スレッドが計算の実行 (またはクエリが他のスレッドから結果を取得するのを待っている) でビジー状態になり、[キャンセル] ボタンのクリックに応答できないことを意味します。

クエリ結果を繰り返し処理する作業をバックグラウンド スレッドにパントする必要があります。

于 2011-08-02T17:32:23.400 に答える
-1

LINQ/PLINQの概念により適したエレガントなソリューションを見つけたと思います。

拡張メソッドを宣言しています。

public static class ProcessWindowsMessagesExtension
{
    public static ParallelQuery<TSource> DoEvents<TSource>(this ParallelQuery<TSource> source)
    {
        return source.Select(
            item =>
            {
                Application.DoEvents();
                Thread.Yield();
                return item;
            });
    }
}

そして、レスポンシブにしたいところならどこでもそれをクエリに追加するよりも。

var result = Directory
            .EnumerateFiles(@"c:\temp", "*.txt", SearchOption.AllDirectories)
            .AsParallel()
            .WithCancellation(m_CancellationTokenSource.Token)
            .SelectMany(File.ReadLines)
            .DoEvents()
            .SelectMany(ReadWords)
            .GroupBy(word => word, (word, words) => new Tuple<int, string>(words.Count(), word))
            .OrderByDescending(occurrencesWordPair => occurrencesWordPair.Item1)
            .Take(20);

それはうまくいきます!

詳細と使用できるソースコードについては、私の投稿を参照してください:「可能であればキャンセルしてください」またはWinFormsでのPLINQのキャンセル可能性と応答性

于 2011-08-03T05:19:35.493 に答える