我正在开发一个用 c# 编写的控制台应用程序
这个应用程序的目的是遍历所有驱动器和文件,并对它们做一些事情。但是用一个线程浏览所有文件是一个耗时的过程,这不是我的目标。
所以我决定用ThreadPool
来处理它:
class Program () {
static void Main(string[] args) {
foreach (var d in DriveInfo.GetDrives()) {
ThreadPool.QueueUserWorkItem(x => Search(d.RootDirectory.GetDirectories()));
}
Console.WriteLine("Job is done.");
Console.ReadKey();
}
private static void Search(DirectoryInfo[] dirs) {
foreach (var dir in dirs) {
try {
foreach (var f in dir.GetFiles()) {
ThreadPool.QueueUserWorkItem(x => DoTheJob(f));
}
ThreadPool.QueueUserWorkItem(x => Search(dir.GetDirectories()));
} catch (Exception ex) {
continue;
}
}
}
}
问题是Console.WriteLine("Job is done.")
在所有线程完成之前执行。我已经阅读了一些问题和答案,但没有一个解决我的问题。
如何在ThreadPool
中的所有线程完成其工作后调用方法?
注意:您可能知道,我不知道将创建多少个线程,因为我不知道有多少个文件。并且设置超时不是一种选择。
使用 QueueUserWorkItem() 是低级的准系统方法。由于无法控制您的工作,那就是一劳永逸。
Task
运行在ThreadPool之上,async/await
可以在这里解决您的问题。
顶层:
var tasks = new List<Task>();
foreach (var d in DriveInfo.GetDrives())
{
tasks.Add( Search(d.RootDirectory.GetDirectories()));
}
Task.WaitAll(tasks.ToArray());
然后你搜索()变成
private static async Task Search(DirectoryInfo[] dirs)
{
...
foreach(...)
{
await Task.Run(...);
}
await Search(dir.GetDirectories());
}
DoTheJob() 的东西应该理想地使用异步 I/O,否则你可以await Task.Run( () => DoTheJob(f))
下面是如何使用Parallel.ForEach
产生公平负载的示例:
static IEnumerable<FileSystemInfo> GetFileSystemObjects(DirectoryInfo dirInfo)
{
foreach (var file in dirInfo.GetFiles())
yield return file;
foreach (var dir in dirInfo.GetDirectories())
{
foreach (var fso in GetFileSystemObjects(dir))
yield return fso;
yield return dir;
}
}
static void Main(string[] args)
{
var files = GetFileSystemObjects(new DirectoryInfo(<some path>)).OfType<FileInfo>();
Parallel.ForEach(files, f =>
{
DoTheJob(f);
});
}
但是,如果DoTheJob
包含 I/O 绑定操作,我会考虑按照Henk Holterman的建议await
处理它,因为Parallel.ForEach
与 I/O 负载无关。