取消WinForms应用程序中的PLINQ查询

我正在开发一个应用程序,该应用程序处理大量文本数据,收集有关单词出现的统计信息(请参阅:Source Code Word Cloud).

在这里,我的代码的简化核心正在做什么.

>列举所有带有* .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();
                }
            }
        }
    }
}

解决方法:

正如乔恩所说,您需要在后台线程上启动PLINQ操作.这样,用户界面在等待操作完成之前不会挂起(因此可以调用Cancel按钮的事件处理程序,并调用取消令牌的Cancel方法).当令牌被取消时,PLINQ查询会自动取消自身,因此您不必为此担心.

这是执行此操作的一种方法:

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);
      }
    }));
  });
}
上一篇:c#-当存在实体框架时我们仍然需要绑定源


下一篇:C#-自定义用户控件中的“覆盖字体”属性未显示在设计器文件中