在后台运行复杂查询时保持UI稳定

本文关键字:UI 稳定 查询 后台 运行 复杂 | 更新日期: 2023-09-27 17:49:01

上下文: c#, Microsoft .NET Framework 4.0, WinForms

问题:当我运行一个巨大的查询时,用户界面挂起。

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

我的代码如下:
return Directory.GetFiles(path, "*.txt", SearchOption.AllDirectories).Any();

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

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

如何保存我的FileChooser窗口挂起,当我运行Directory.GetFiles() ?

谢谢。

在后台运行复杂查询时保持UI稳定

您应该使用任务:

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

然后使用它和一个延续来处理结果:

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

您正在从UI线程调用您的方法。由于这个原因,如果该方法是一个耗时的任务,那么UI将冻结并且应用程序似乎没有响应。

使用BackgroundWorker线程(如我在这里建议的)或使用Task库,以便让你的方法异步执行而不冻结UI。

你可以在这里找到一个关于BackgroundWorker的例子。

正如@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());
    });
}
  • 添加一个BackgroundWorker组件到WinForm
  • 把耗时的代码放在后台工作线程的DoWork事件中
  • 你可以使用RunWorkerCompleted事件在工作完成后执行代码
  • 如果你想让UI报告长时间运行代码的进度,你可以使用ProgressChanged事件。

正如其他人建议的那样,您可以使用后台worker来执行此任务。但是,我们还不知道您是否希望允许用户在执行此任务期间使用应用程序。如果你能回答这个问题,那么回应将更适合你的需求。然后,你可以继续使用线程方法,或者必须制作一些像启动屏幕这样的东西来告诉用户应用程序正在做某事。

在那之前,你可以做一些事情来加速搜索:

首先,您确实不想知道这些目录有多少文本文件。一旦找到一个,进程就会返回。看看这三种方法:

///
/// 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方法更快的方法,那么它确实比第一种方法性能更好。