我想从Windows服务中查询远程目录中的新文件(pdf为几MB)。
每个文件都必须由密集的CPU作业处理(pdf文件中的图像识别)。完成此过程后,必须将文件移动到其他位置或删除。
我希望尽快启动我的工作,同时使多处理器功能受益于parallalise作品。
但是,我遇到了一个问题:虽然枚举目录中的文件很容易,但如何避免作业队列中的重复条目?实际上,每次枚举我的文件时,有些文件可能已排队或尚未处理。
我的第一种方法是调查System.Collections.Concurrent.*
但是在添加之前没有类似乎提供了要测试的contains方法。
我也看了HashSet<string>
,但我担心并发访问会遇到一些问题。
我目前的骨架是:
private async void GetNewFiles(CancellationToken cancellationToken)
{
if (!cancellationToken.IsCancellationRequested)
{
var newfiles = Directory.GetFileSystemEntries(inputDirectory, "*.pdf", SearchOption.AllDirectories);
logger.Trace($"{newfiles.Length} new files detected in {inputDirectory}");
foreach (var file in newfiles)
{
Task.Factory.StartNew(()=>ProcessFile(file), cancellationToken);
}
await Task.Delay(frequency, cancellationToken);
if (!cancellationToken.IsCancellationRequested)
{
GetNewFiles(cancellationToken);
}
}
}
但是,此代码不会避免文件排队两次。
如果我删除Task.Delay
调用并等待处理所有文件,它会起作用,但它可能导致只有一个正在运行的任务,即使添加了新文件(每次迭代处理新文件)必须在检查新文件之前完全处理。)
答案 0 :(得分:1)
对您当前代码进行最少量修改的最简单方法是使用ConcurrentDictionary
我认为:
private readonly ConcurrentDictionary<string, byte> _filesInProgress = new ConcurrentDictionary<string, byte>();
private async Task GetNewFiles(CancellationToken cancellationToken) {
if (!cancellationToken.IsCancellationRequested) {
var newfiles = Directory.GetFileSystemEntries(inputDirectory, "*.pdf", SearchOption.AllDirectories);
foreach (var file in newfiles) {
// TryAdd returns true if key was not already in dictionary
if (_filesInProgress.TryAdd(file, 0) && File.Exists(file)) {
Task.Factory.StartNew(() => {
ProcessFile(file);
_filesInProgress.TryRemove(file, out _);
}, cancellationToken);
}
}
await Task.Delay(frequency, cancellationToken);
if (!cancellationToken.IsCancellationRequested) {
GetNewFiles(cancellationToken);
}
}
}
请注意,理想情况下,您需要处理线程数量有限的项目(等于核心数/虚拟核心数)。截至目前,如果您在目录中找到100个文件,您可能会产生100个线程,这些线程都是CPU密集的,因此无缘无故地相互竞争资源。