异步阻止GUI线程,WinForms的问题

时间:2017-10-25 22:19:57

标签: c# .net winforms asynchronous

我遇到一个奇怪的问题, 应该工作(据我所知),但事实并非如此。

我有一个Windows窗体应用程序。它有一个按钮,可以扫描选定的目录,这可能会导致找到大量文件。扫描~50k文件时,大约需要10秒钟。

我尝试实现异步,将文件扫描本身异步运行到主GUI,但这会造成麻烦。任务本身运行正常,但它仍然阻止了GUI,导致整个应用程序冻结。

这是按钮的代码

private async void BeginScanButton_Click(object sender, EventArgs e)
    {
        if (_osuDirectory == null)
            MessageBox.Show("You have not chosen an Osu! directory yet.");
        else
        {
            await ScanFilesTask();
        }

还有很多,但这是它的相关部分。

任务本身是

 private Task ScanFilesTask()
    {
        FileList.Clear();
            return Task.Run(() =>
            {
                if (_jpgFilesChecked)
                    FileParser.ParseFiles(_osuDirectory, "*.jpg");
                if (_pngFilesChecked)
                    FileParser.ParseFiles(_osuDirectory, "*.png");
                if (_wavFilesChecked)
                    FileParser.ParseFiles(_osuDirectory, "*.wav");
                if (_aviFilesChecked)
                    FileParser.ParseFiles(_osuDirectory, ".avi");
            });
    }

最后,FileParser类只是

        public static void ParseFiles(string dir, string extension)
        {
            Form1.FileList.AddRange(Directory.GetFiles(dir, extension, SearchOption.AllDirectories));
        }

(如果有人想查看,所有代码都在github repo上。相关的异步代码在AsyncTest分支中。)

非常感谢任何帮助。这是我的第一个真正的C#项目,非常感谢任何指向正确的方向。提前谢谢!

2 个答案:

答案 0 :(得分:1)

要阅读的内容:

Task.Run Etiquette and Proper Usage

Task.Run vs BackgroundWorker, Round 1: The Basic Pattern

A Tour of Task, Part 0: Overview

摘录:

  
      
  • 自.NET 4.0中引入以来使用过Task和TPL(任务并行库)的开发人员。这些开发人员熟悉Task以及如何在并行处理中使用它。这些开发人员面临的危险是Task(因为它被TPL使用)与Task完全不同(因为它被async使用)。

  •   
  • 在异步出现之前从未听说过Task的开发人员。对他们来说,Task只是异步的一部分 - 还有一个(相当复杂的)要学习的东西。 “延续”是一个外来词。这些开发人员面临的危险是假设Task的每个成员都适用于异步编程,但肯定不是这样。

  •   

Async中没有意义,你想要的是并行运行。他们是非常不同的。您正在寻找的是:

按钮:

private void BeginScanButton_Click(object sender, EventArgs e)
{
    BeginScanButton.Enabled = false;
    if (_osuDirectory == null)
        MessageBox.Show("You have not chosen an Osu! directory yet.");
        BeginScanButton.Enabled = false;
    else
    {
        ScanFilesInParallel();
    }
}

ScanFiles

private void ScanFilesInParallel()
{
    FileList.Clear();
    Task.Run(() =>
    {
      var result = new List<string>();

      if (_jpgFilesChecked)
        result.AddRange(FileParser.ParseFiles(_osuDirectory, "*.jpg"));
      if (_pngFilesChecked)
        result.AddRange(FileParser.ParseFiles(_osuDirectory, "*.png"));
      if (_wavFilesChecked)
        result.AddRange(FileParser.ParseFiles(_osuDirectory, "*.wav"));
      if (_aviFilesChecked)
        result.AddRange(FileParser.ParseFiles(_osuDirectory, ".avi"));

      return result;
     })
     .ContinueWith((task) => {
       FileList.AddRange(task.Result);
       BeginScanButton.Enabled = true;
     }, TaskScheduler.FromCurrentSynchronizationContext());
}

ParseFiles

public IEnumerable<string> ParseFiles(string dir, string extension)
{
  var result = Directory.GetFiles(dir, extension, SearchOption.AllDirectories).ToList();

  return result;
}

答案 1 :(得分:0)

只是给你一个我认为读得更好的选择;尝试使用Microsoft的Reactive Framework。

然后你可以这样做:

private async void BeginScanButton_Click(object sender, EventArgs e)
{
    if (_osuDirectory == null)
        MessageBox.Show("You have not chosen an Osu! directory yet.");
    else
    {
        var exts = new[]
        {
            _jpgFilesChecked ? "*.jpg" : null,
            _pngFilesChecked ? "*.png" : null,
            _wavFilesChecked ? "*.wav" : null,
            _aviFilesChecked ? "*.avi" : null,
        }.Where(x => x != null);

        var query =
            from ext in exts.ToObservable()
            from fs in Observable.Start(() => FileParser.ParseFiles(_osuDirectory, ext))
            from f in fs
            select f;

        var files = await query.ToArray();

        FileList.AddRange(files);
    }
}

public static class FileParser
{
    public static string[] ParseFiles(string dir, string extension)
    {
        return Directory.GetFiles(dir, extension, SearchOption.AllDirectories);
    }
}

它们全部并行处理,并在UI线程上完成。

NuGet&#34; System.Reactive&#34;,&#34; System.Reactive.Windows.Forms&#34;和/或&#34; System.Reactive.Windows.Threading&#34; (WPF)得到这些位。