【问题标题】:Keep UI stable running complex queries in background保持 UI 稳定在后台运行复杂的查询
【发布时间】:2014-03-08 14:09:38
【问题描述】:

上下文: C#、Microsoft .NET Framework 4.0、WinForms

问题:当我运行大量查询时,用户界面挂起。

说明: 在我的应用程序中,我允许用户将文本文件存储在特定目录中。但在存储文件之前,我必须确保该目录及其子目录没有文本文件。为此,我必须遍历目录并确保目录树没有*.txt 文件。

我的代码如下:

return Directory.GetFiles(path, "*.txt", SearchOption.AllDirectories).Any();

如果目录树很大,我的 UI 就会挂起,很残酷。

我尝试的替代方法是在上面的 LINQ 查询中添加一个 AsParallel() 调用,但这对我没有帮助。

当我运行Directory.GetFiles() 时,如何避免我的 FileChooser 窗口挂起?

谢谢。

【问题讨论】:

  • 我在我的 winforms 应用程序中大量使用后台工作程序组件。

标签: c# .net winforms linq asynchronous


【解决方案1】:

你应该为此使用任务:

private Task<bool> GetFilesAsync(string path)
{
    return Task.Factory.StartNew(() => Directory.GetFiles(path, "*.txt", SearchOption.AllDirectories).Any(), 
        TaskCreationOptions.LongRunning);
}

然后用一个continuation来处理结果:

GetFilesAsync(path).ContinueWith(parentTask => 
{
        // Code which check that parentTask.Exception is null, and then use
        // parentTask.Result
}, TaskScheduler.FromCurrentSynchronizationContext());

【讨论】:

  • 您的代码似乎无法编译,因为在 StartNew 中您使用的是 Func 委托并且您没有返回任何内容。您必须在Directory.GetFiles 之前添加return 否则会出现错误:Not all code paths return a value in lambda expression of type 'System.Func&lt;bool&gt;'
  • 其实没有,你可以测试一下。这是单行 lambda 表达式,它返回单行表达式的值。实际上,如果不将其包含在{ } 中,则返回会产生编译错误。如果你使用过 LINQ,你应该熟悉这样的 lambdas
【解决方案2】:

您正在从 UI 线程调用您的方法。因此,如果该方法是一项耗时的任务,则 UI 会冻结并且应用程序似乎没有响应。

使用BackgroundWorker 线程(正如我建议的here)或使用Task library 以便让您的方法异步执行而不会冻结UI。

您可以找到有关 BackgroundWorker here 的示例。

正如@ShlomiBorovitz 所指出的(我同意),使用 BackgroundWorker 并不是一个优雅的解决方案,因为需要维护的元素太多。 由于您使用的是 .NET Framework 4,因此最好的解决方案是使用 Tasks 库。更少需要维护的代码和更少的麻烦。

Task 库的使用示例如下:

private Task<bool> SearchFilesAsync(string path)
{
    return Task<bool>.Factory.StartNew(() =>
    {
         return Directory.GetFiles(path, "*.txt", SearchOption.AllDirectories).Any();
    });

}

private void CheckTxtFile()
{
    string myPath = @"SearchPathHere";
    SearchFilesAsync(myPath).ContinueWith(myTask =>
    {
         Console.WriteLine("Text file found : {0}", myTask.Result.ToString());
    });
}

【讨论】:

  • 这行不通。 Task.Wait(myTask); 正在阻塞,这意味着 UI 线程将被阻塞......与 OP 相同的问题。它也不会编译,因为它应该是myTask.Wait();
  • @ShlomiBorovitz 该死的......谢谢。我纠正了我的错误!
  • 我把投票改掉了......不过,我不推荐在 .Net 4.0 中使用 backgroundworker
  • @ShlomiBorovitz 是的,我现在注意到了。我知道这不是一个优雅的解决方案,但它确实有效。这是另一种选择。
  • @ShlomiBorovitz 对。我添加了一条注释!
【解决方案3】:
  • 在 WinForm 中添加 BackgroundWorker 组件
  • 将耗时代码放入BackgroundWorker线程的DoWork事件中
  • 工作完成后,您可以使用RunWorkerCompleted 事件执行代码
  • 如果您希望 UI 报告长时间运行的代码的进度,您可以使用 ProgressChanged 事件。

【讨论】:

  • 我不建议在 .Net 4.0 中使用 backgroundworker
  • 你会推荐什么?
  • 任务和整个 TPL 提供了更优雅、(更重要的是)可维护的解决方案。
【解决方案4】:

正如其他人所建议的,您可以使用后台工作人员来执行此任务。但是,我们还不知道您是否要允许用户在执行此任务期间使用应用程序。如果您能回答这个问题,那么响应将更适合您的需求。然后,您可以继续使用线程方法,或者必须制作诸如闪屏之类的东西来告诉用户应用程序正在做某事。

在此之前,您可以采取以下措施来加快搜索速度:

首先,您真的不想知道这些目录有多少文本文件。一旦你找到一个,你的进程应该返回。看看这三种方法:

///
/// This is similar to what you have been doing. It just uses one less extension method call
///       
private bool SkipDirectory()
    {
        return Directory.GetFiles(@"C:\Program Files", "*.txt", SearchOption.AllDirectories).Length > 0;
    }

///
/// The EnumerateFileSystemEntries method itself is quicker than GetFiles but Count method makes it slower
///    
private bool SkipDirectoryEnumerableMethod()
    {
        return (Directory.EnumerateFileSystemEntries(@"C:\Program Files", "*.txt", SearchOption.AllDirectories).Count() > 0);
    }


    ///
    /// This method only search till the first occurrence of text file is spotted.
    private bool SkipDirectory(string path)
    {
        IEnumerable<string> directoryPaths = null;
        bool hasTextFile = false;

        if (directoryPaths == null)
        {
            directoryPaths = Directory.EnumerateDirectories(path, "*", SearchOption.AllDirectories);
        }


        foreach (string directoryPath in directoryPaths)
        {
            IEnumerable<string> files = Directory.EnumerateFileSystemEntries(directoryPath, "*.txt", SearchOption.TopDirectoryOnly);
            if (hasTextFile = (files.Count() > 0))
                break;
        }

        return hasTextFile;
    }

在我的电脑上,我看到了一些时间跨度值:

Method One: 00:00:00.3300189
Method Two: 00:00:00.3590205
Method Three: 00:00:00.0010001

不要对第三次跨度过于兴奋。目录结构中可能有一个文本文件。我没有设置目录来正确验证性能,但乍一看确实显示了一些有趣的东西。对于第二种方法,我不确定如何使用,但如果可以使用比Count 方法更快的方法,那么它的性能确实比第一种更好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-06
    • 1970-01-01
    • 1970-01-01
    • 2020-08-08
    相关资源
    最近更新 更多