【问题标题】:Calculating folder size / Enumerate filesystem计算文件夹大小/枚举文件系统
【发布时间】:2022-01-07 02:53:48
【问题描述】:

我正在尝试计算文件夹大小,但问题是;它在 D:\ 驱动器或其他文件夹中运行得很快,但是每当我尝试单击 C:\ 驱动器时,应用程序会冻结大约 7-8 秒。 (我的驱动器列表在树视图上) 当我删除文件夹大小时,一切正常。大家对此有什么想法吗?

   public FolderModel(string folderPath)
    {
        try
        {

            //File = new FileInfo(folderPath);
            //FolderInfo = new DirectoryInfo(folderPath);
            //_createdTime = FolderInfo.CreationTime.ToShortDateString();
            //_folderName = FolderInfo.Name;
            //_folderPath = folderPath;
            //Fileextension = File.Extension.ToLower();
            //this.Children = new ObservableCollection<FolderModel>();

            _folderSize = CalculatorSize(GetDirectorySize(folderPath));
           
        }
        catch (Exception e)
        {
            //
        }
    }




    internal string CalculatorSize(long bytes)
    {
        var suffix = new[] { "B", "KB", "MB", "GB", "TB" };
        float byteNumber = bytes;
        for (var i = 0; i < suffix.Length; i++)
        {
            if (byteNumber < 1000)
            {
                if (i == 0)
                    return $"{byteNumber} {suffix[i]}";
                else
                    return $"{byteNumber:0.#0} {suffix[i]}";
            }
            else
            {
                byteNumber /= 1024;
            }
        }
        return $"{byteNumber:N} {suffix[suffix.Length - 1]}";
    }



    internal static long GetDirectorySize(string directoryPath)
    {
        try
        {
            if (Directory.Exists(directoryPath))
            {
                var d = new DirectoryInfo(directoryPath);
                return d.EnumerateFiles("*", SearchOption.AllDirectories).Sum(fi => fi.Length);
            }

            return new FileInfo(directoryPath).Length;
        }
        catch (UnauthorizedAccessException)
        {
            return 0;
        }
        catch (FileNotFoundException)
        {
            return 0;
        }
        catch (DirectoryNotFoundException)
        {
            return 0;
        }
    }

【问题讨论】:

  • 利用异步编程。即使您仍在忙于计算,这也会使 UI 响应。作为参考检查这个link
  • 您只对驱动器感兴趣吗?您使用的是哪个 .NET 版本?
  • @BionicCode,我使用的是 .Net Framework 4.7.2 不,不仅是驱动器。我有包含桌面、文档、文件夹、目录、驱动器的树视图......但只有当我展开 C:\ 驱动器时它才会冻结。 7-8 秒后,它会扩展,当我扩展 childerens 时,它会再次冻结。
  • @Harish 谢谢,我试过了,但结果是一样的。或者也许我做不到。

标签: c# wpf directory size freeze


【解决方案1】:

您必须在后台线程上枚举文件夹。

提高性能的建议
使用DriveInfo API 时,您可以进一步提高文件夹路径为驱动器的情况下的性能。在这种情况下,您可以省略整个驱动器的枚举,这通常需要一段时间。
此外,当枚举抛出UnauthorizedAccessException 异常时,您当前的实现会中止计算。你不想要那个。您希望算法忽略禁止的文件系统路径。

以下两个示例显示了您的实施的固定和改进版本。
第一个解决方案针对现代 .NET Standard 2.1 兼容的 .NET 版本。
第二种解决方案针对的是旧的 .NET Framework。

.NET Standard 2.1(.NET Core 3.0、.NET 5)

当使用与 .NET Core 3.0 和 .NET 5 等与 .NET Standard 2.1 兼容的 .NET 版本时,您可以消除异常处理。使用EnumerationOptions 作为参数允许API 忽略不可访问的目录,从而显着提高性能(不再出现UnauthorizedAccessException 异常)和可读性:

internal static async Task<bool> TryGetDirectorySize(string directoryPath, out long spaceUsedInBytes)
{
  spaceUsedInBytes = -1;
  var drives = DriveInfo.GetDrives();
  DriveInfo targetDrive = drives.FirstOrDefault(drive => drive.Name.Equals(directoryPath, StringComparison.OrdinalIgnoreCase));

  // Directory is a drive: skip the expensive enumeration of complete drive.
  if (targetDrive != null)
  {
    spaceUsedInBytes = targetDrive.TotalSize - targetDrive.TotalFreeSpace;
    return true;
  }

  if (!Directory.Exists(folderPath))
  {
    return false;
  }

  // Consider to make this local variable a private property
  var enumerationOptions = new EnumerationOptions { RecurseSubdirectories = true };

  var targetFolderInfo = new DirectoryInfo(directoryPath);
  spaceUsedInBytes = await Task.Run(
    () => targetFolderInfo.EnumerateFiles("*", enumerationOptions)
      .Sum(fileInfo => fileInfo.Length));

  return true;
}

.NET 框架

符合 .NET Framework 的版本。它解决了您的原始代码的问题,即一旦抛出 UnauthorizedAccessException 异常,枚举就会中止。此版本继续使用递归枚举所有剩余目录:

internal static async Task<long> GetDirectorySize(string directoryPath)
{
  long spaceUsedInBytes = -1;
  var drives = DriveInfo.GetDrives();
  DriveInfo targetDrive =  drives.FirstOrDefault(drive => drive.Name.Equals(directoryPath, StringComparison.OrdinalIgnoreCase));

  // Directory is a drive: skip enumeration of complete drive.
  if (targetDrive != null)
  {
    spaceUsedInBytes = targetDrive.TotalSize - targetDrive.TotalFreeSpace;
    return spaceUsedInBytes;
  }

  var targetDirectoryInfo = new DirectoryInfo(directoryPath);
  spaceUsedInBytes = await Task.Run(() => SumDirectorySize(targetDirectoryInfo));
  return spaceUsedInBytes;
}

private static long SumDirectorySize(DirectoryInfo parentDirectoryInfo)
{
  long spaceUsedInBytes = 0;
  try
  {
    spaceUsedInBytes = parentDirectoryInfo.EnumerateFiles("*", SearchOption.TopDirectoryOnly)
      .Sum(fileInfo => fileInfo.Length);
  }
  catch (UnauthorizedAccessException)
  {
    return 0;
  }

  foreach (var subdirectoryInfo in parentDirectoryInfo.EnumerateDirectories("*", SearchOption.TopDirectoryOnly))
  {
    spaceUsedInBytes += SumDirectorySize(subdirectoryInfo);
  }

  return spaceUsedInBytes;
}

如何实例化需要在构造时运行异步操作的类型

FolderModel.cs

class FolderModel
{
  // Make a constructor private to force instantiation using the factory method
  private FolderModel(string folderPath)
  {
    // Do non-async initialization
  }

  // Async factory method: add constructor parameters to async factory method
  public static async Task<FolderModel> CreateAsync(string folderPath)
  {
    var instance = new FolderModel(folderPath);
    await instance.InitializeAsync(folderPath);
    return instance;
  }

  // Define member as protected virtual to allow derived classes to add initialization routines
  protected virtual async Task InitializeAsync(string directoryPath)
  {
    // Consider to throw an exception here ONLY in case the folder is generated programmatically.
    // If folder is retrieved from user input, use input validation 
    // or even better use a folder picker dialog
    // to ensure that the provided path is always valid!
    if (!Directory.Exists(directoryPath))
    {
      throw new DirectoryNotFoundException($"Invalid directory path '{directoryPath}'.");
    }

    long folderSize = await GetDirectorySize(directoryPath);

    // TODO::Do something with the 'folderSize' value 
    // and execute other async code if necessary
  }
}

用法

// Create an instance of FolderModel example
private async Task SomeMethod()
{
  // Always await async methods (methods that return a Task).
  // Call static CreateAsync method instead of the constructor.
  FolderModel folderModel = await FolderModel.CreateAsync(@"C:\");
}

在更高级的场景中,当您想推迟初始化时,例如因为您想避免分配现在不需要或永远不需要的昂贵资源,您可以在某个成员依赖于引用这些资源,或者您可以将构造函数和InitializeAsync 方法设为公共,以允许该类的用户显式调用InitializeAsync

【讨论】:

  • You must enumerate the folder on a background thread. -- 或者您可以使用here 描述的异步等待方法,它在same 线程上运行。
  • @RobertHarvey 我的例子完全一样:它使用await Task.Run。虽然 async/await 不一定在不同的线程上运行,但使用 Task.Run 开始的操作可以。自然或纯异步方法不需要额外的线程,这是真的。 Task.Run 可以。据我所知,您提供的示例使用 Task.Run,​​因此它不会在调用者的线程上下文中运行。
  • @SerhatÖzyıldız 不要忘记等待 GetDirectorySize() 调用! await GetDirectorySize(@"C:\");
  • @SerhatÖzyıldız 我已经更新了答案,为您提供了一个如何实例化需要异步初始化的类的示例。我还将Directory.Exists 检查从GetDirectorySize() 方法移到了实例化例程中。您应该使用用户输入验证,或者最好使用文件夹选择器对话框:
  • @SerhatÖzyıldız 通过 NuGet 管理器安装 Windows-API-Code-Pack-1.1.4 并使用 CommonOpenFileDialog 或使用 FolderBrowserDialog
猜你喜欢
  • 2011-08-17
  • 2013-07-29
  • 2011-11-24
  • 1970-01-01
  • 2019-01-06
  • 2016-09-10
  • 1970-01-01
  • 2016-08-20
相关资源
最近更新 更多