多个线程同时搜索同一个文件夹
Multiple Threads searching on same folder at same time
目前我有一个包含大约 170,000 个 jpg 文件名的 .txt 文件,我将它们全部读入一个列表 (fileNames)。
我想搜索一个文件夹(此文件夹有子文件夹)以检查此文件夹中是否存在 fileNames 中的每个文件,如果存在,则将其复制到新文件夹中。
我粗略估计了一下,每次搜索和复制 fileNames 中的每个文件名大约需要 0.5 秒。所以 170,000 秒大约是 48 小时,所以除以 2,我的应用程序使用 1 个线程搜索每个文件名大约需要 24 小时!显然这太长了,所以我想缩小范围并加快流程。使用多线程执行此操作的最佳方法是什么?
目前我正在考虑制作 20 个单独的线程并将我的列表(文件名)拆分为 20 个不同的列表并同时搜索文件。例如,我将有 20 个不同的线程同时执行以下操作:
foreach (string str in fileNames)
{
foreach (var file in Directory.GetFiles(folderToCheckForFileName, str, SearchOption.AllDirectories))
{
string combinedPath = Path.Combine(newTargetDirectory, Path.GetFileName(file));
if (!File.Exists(combinedPath))
{
File.Copy(file, combinedPath);
}
}
}
已更新以在下方显示我的解决方案:
string[] folderToCheckForFileNames = Directory.GetFiles("C:\Users\Alex\Desktop\ok", "*.jpg", SearchOption.AllDirectories);
foreach(string str in fileNames)
{
Parallel.ForEach(folderToCheckForFileNames, currentFile =>
{
string filename = Path.GetFileName(currentFile);
if (str == filename)
{
string combinedPath = Path.Combine(targetDir, filename);
if (!File.Exists(combinedPath))
{
File.Copy(currentFile, combinedPath);
Console.WriteLine("FOUND A MATCH AND COPIED" + currentFile);
}
}
}
);
}
感谢大家的贡献!非常感谢!
如果您的计算机的内核少于 20 个,则 20 个不同的线程将无济于事。事实上,它会使过程变慢,因为你将 1) 必须花时间在每个线程之间切换上下文(这是你的 CPU 模拟超过 1 个线程/核心的方式)和 2) [= .NET 中的 11=] 为其堆栈保留 1 MB,这是相当大的。
相反,尝试将您的 I/O 分成 async
工作负载,使用 Task.Run
作为 CPU 绑定/密集部分。此外,将 Tasks
的数量保持在最多 4 到 8 个。
示例代码:
var tasks = new Task[8];
var names = fileNames.ToArray();
for (int i = 0; i < tasks.Length; i++)
{
int index = i;
tasks[i] = Task.Run(() =>
{
for (int current = index; current < names.Length; current += 8)
{
// execute the workload
string str = names[current];
foreach (var file in Directory.GetFiles(folderToCheckForFileName, str, SearchOption.AllDirectories))
{
string combinedPath = Path.Combine(newTargetDirectory, Path.GetFileName(file));
if (!File.Exists(combinedPath))
{
File.Copy(file, combinedPath);
}
}
}
});
}
Task.WaitAll(tasks);
您应该使用并行 linq,而不是使用普通的 foreach 语句进行搜索。 Parallel linq 结合了 LINQ 语法的简单性和可读性以及并行编程的强大功能。就像针对任务并行库的代码一样。这将使您免受低级线程操作和可能的异常(难以 find/debug 异常)的影响,同时将您的工作分散到多个线程中。所以你可以这样做:
fileNames.AsParallel().ForAll(str =>
{
var files = Directory.GetFiles(folderToCheckForFileName, str, SearchOption.AllDirectories);
files.AsParallel().ForAll(file =>
{
if (!string.IsNullOrEmpty(file))
{
string combinedPath = Path.Combine(newTargetDirectory, Path.GetFileName(file));
if (!File.Exists(combinedPath))
{
File.Copy(file, combinedPath);
}
}
});
});
目前我有一个包含大约 170,000 个 jpg 文件名的 .txt 文件,我将它们全部读入一个列表 (fileNames)。
我想搜索一个文件夹(此文件夹有子文件夹)以检查此文件夹中是否存在 fileNames 中的每个文件,如果存在,则将其复制到新文件夹中。
我粗略估计了一下,每次搜索和复制 fileNames 中的每个文件名大约需要 0.5 秒。所以 170,000 秒大约是 48 小时,所以除以 2,我的应用程序使用 1 个线程搜索每个文件名大约需要 24 小时!显然这太长了,所以我想缩小范围并加快流程。使用多线程执行此操作的最佳方法是什么?
目前我正在考虑制作 20 个单独的线程并将我的列表(文件名)拆分为 20 个不同的列表并同时搜索文件。例如,我将有 20 个不同的线程同时执行以下操作:
foreach (string str in fileNames)
{
foreach (var file in Directory.GetFiles(folderToCheckForFileName, str, SearchOption.AllDirectories))
{
string combinedPath = Path.Combine(newTargetDirectory, Path.GetFileName(file));
if (!File.Exists(combinedPath))
{
File.Copy(file, combinedPath);
}
}
}
已更新以在下方显示我的解决方案:
string[] folderToCheckForFileNames = Directory.GetFiles("C:\Users\Alex\Desktop\ok", "*.jpg", SearchOption.AllDirectories);
foreach(string str in fileNames)
{
Parallel.ForEach(folderToCheckForFileNames, currentFile =>
{
string filename = Path.GetFileName(currentFile);
if (str == filename)
{
string combinedPath = Path.Combine(targetDir, filename);
if (!File.Exists(combinedPath))
{
File.Copy(currentFile, combinedPath);
Console.WriteLine("FOUND A MATCH AND COPIED" + currentFile);
}
}
}
);
}
感谢大家的贡献!非常感谢!
如果您的计算机的内核少于 20 个,则 20 个不同的线程将无济于事。事实上,它会使过程变慢,因为你将 1) 必须花时间在每个线程之间切换上下文(这是你的 CPU 模拟超过 1 个线程/核心的方式)和 2) [= .NET 中的 11=] 为其堆栈保留 1 MB,这是相当大的。
相反,尝试将您的 I/O 分成 async
工作负载,使用 Task.Run
作为 CPU 绑定/密集部分。此外,将 Tasks
的数量保持在最多 4 到 8 个。
示例代码:
var tasks = new Task[8];
var names = fileNames.ToArray();
for (int i = 0; i < tasks.Length; i++)
{
int index = i;
tasks[i] = Task.Run(() =>
{
for (int current = index; current < names.Length; current += 8)
{
// execute the workload
string str = names[current];
foreach (var file in Directory.GetFiles(folderToCheckForFileName, str, SearchOption.AllDirectories))
{
string combinedPath = Path.Combine(newTargetDirectory, Path.GetFileName(file));
if (!File.Exists(combinedPath))
{
File.Copy(file, combinedPath);
}
}
}
});
}
Task.WaitAll(tasks);
您应该使用并行 linq,而不是使用普通的 foreach 语句进行搜索。 Parallel linq 结合了 LINQ 语法的简单性和可读性以及并行编程的强大功能。就像针对任务并行库的代码一样。这将使您免受低级线程操作和可能的异常(难以 find/debug 异常)的影响,同时将您的工作分散到多个线程中。所以你可以这样做:
fileNames.AsParallel().ForAll(str =>
{
var files = Directory.GetFiles(folderToCheckForFileName, str, SearchOption.AllDirectories);
files.AsParallel().ForAll(file =>
{
if (!string.IsNullOrEmpty(file))
{
string combinedPath = Path.Combine(newTargetDirectory, Path.GetFileName(file));
if (!File.Exists(combinedPath))
{
File.Copy(file, combinedPath);
}
}
});
});