C# 爽点记录-2:并行任务

之前有个小需求,想写个小工具来统计电脑上各个文件夹和文件的大小,方便自己找出占用空间多的文件夹进行清理,释放硬盘空间。代码实现起来很简单,利用各种编程语言提供的读取文件系统当中的文件和大小接口,然后做个累加统计就可。

具体的思路是:

  1. 读取目录下的所有文件和文件夹
  2. 文件直接获取大小
  3. 文件夹则递归调用计算函数,获取大小
  4. 最后把所有文件和文件夹的大小累加起来

一个函数就能搞定,但是如果当目录下的文件夹数量太多,或者是目录的层级太深,跑起来就会很慢,尤其是通常来说硬盘里面的文件和文件夹数量这么多,随随便便就能跑个几十分钟(还是非磁盘的根目录)。

于是想当然地就想着用多线程来并行计算加速。因为最开始的版本是用 Go 实现,所以就把每次递归调用变成了开一个协程来跑。一开始觉得可能会用太多的协程,占用过多资源,所以还写了个简单的协程池,然而这样限制协程数量的话,导致不够协程来计算新的文件夹,就会出现死锁的情况。所以就还是每个调用开个协程来跑,不过速度还是比较感人。

然后昨晚看到一个 C# 的写法,一个函数就能够就能够简单开启并行任务,也不用像在 Go 里面用各种锁或者 channel 来进行数据同步。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public static long GetDirectorySize(this System.IO.DirectoryInfo directoryInfo, bool recursive = true)
{
    var startDirectorySize = default(long);
    if (directoryInfo == null || !directoryInfo.Exists)
        return startDirectorySize; //Return 0 while Directory does not exist.

    //Add size of files in the Current Directory to main size.
    foreach (var fileInfo in directoryInfo.GetFiles())
        System.Threading.Interlocked.Add(ref startDirectorySize, fileInfo.Length);

    if (recursive) //Loop on Sub Direcotries in the Current Directory and Calculate it's files size.
        System.Threading.Tasks.Parallel.ForEach(directoryInfo.GetDirectories(), (subDirectory) =>
    System.Threading.Interlocked.Add(ref startDirectorySize, GetDirectorySize(subDirectory, recursive)));

    return startDirectorySize;  //Return full Size of this Directory.
}

C# 可以直接使用 System.Threading.Tasks 来并行跑任务,然后通过 System.Threading.Interlocked 直接原子式修改变量,能够直接在几十秒内扫描完 380+g 的整个盘,相比于需要差不多十分钟的Go 版本,速度简直惊人,加上 dotnet 还能直接打包成 exe,用起来体验简直起飞。