У меня есть приложение .NET Core 2.0, в котором я перебираю множество файлов (600 000) разных размеров (всего 220 ГБ).
Я перечисляю их, используя
new DirectoryInfo(TargetPath)
.EnumerateFiles("*.*", SearchOption.AllDirectories)
.GetEnumerator()
и перебирать их, используя
Parallel.ForEach(contentList.GetConsumingEnumerable(),
new ParallelOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount * 2
},
file => ...
Внутри этого у меня есть список выражений регулярных выражений, которые я затем сканирую файл, используя
Parallel.ForEach(_Rules,
new ParallelOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount * 2
},
rule => ...
Наконец, я получаю совпадения, используя экземпляр класса Regex
RegEx = new Regex(
Pattern.ToLowerInvariant(),
RegexOptions.Multiline | RegexOptions.Compiled,
TimeSpan.FromSeconds(_MaxSearchTime))
Этот экземпляр является общим для всех файлов, поэтому я компилирую его один раз. К файлам применяется 175 шаблонов.
В случайных (ish) точках приложение блокируется и полностью не отвечает. Никакое количество попытки / ловли не останавливает это. Если я возьму точно такой же код и скомпилирую его для .NET Framework 4.6, он будет работать без проблем.
Я перепробовал МНОГО вещей, и мой текущий тест, который, кажется, работает (но я очень осторожен!) - НЕ использовать INSTANCE, а вместо этого каждый раз вызывать метод STATIC Regex.Matches
. Я не могу сказать, какой удар я получаю по производительности, но по крайней мере я не получаю тупиков.
Я мог бы использовать некоторую проницательность или, по крайней мере, служить предостерегающим рассказом.
Обновление:
Я получаю список файлов следующим образом:
private void GetFiles(string TargetPath, BlockingCollection<FileInfo> ContentCollector)
{
List<FileInfo> results = new List<FileInfo>();
IEnumerator<FileInfo> fileEnum = null;
FileInfo file = null;
fileEnum = new DirectoryInfo(TargetPath).EnumerateFiles("*.*", SearchOption.AllDirectories).GetEnumerator();
while (fileEnum.MoveNext())
{
try
{
file = fileEnum.Current;
//Skip long file names to mimic .Net Framework deficiencies
if (file.FullName.Length > 256) continue;
ContentCollector.Add(file);
}
catch { }
}
ContentCollector.CompleteAdding();
}
Внутри моего класса Rule есть соответствующие методы:
_RegEx = new Regex(Pattern.ToLowerInvariant(), RegexOptions.Multiline | RegexOptions.Compiled, TimeSpan.FromSeconds(_MaxSearchTime));
...
public MatchCollection Matches(string Input) { try { return _RegEx.Matches(Input); } catch { return null; } }
public MatchCollection Matches2(string Input) { try { return Regex.Matches(Input, Pattern.ToLowerInvariant(), RegexOptions.Multiline, TimeSpan.FromSeconds(_MaxSearchTime)); } catch { return null; } }
Тогда вот соответствующий код:
public List<SearchResult> GetMatches(string TargetPath)
{
//Set up the concurrent containers
ConcurrentBag<SearchResult> results = new ConcurrentBag<SearchResult>();
BlockingCollection<FileInfo> contentList = new BlockingCollection<FileInfo>();
//Start getting the file list
Task collector = Task.Run(() => { GetFiles(TargetPath, contentList); });
int cnt = 0;
//Start processing the files.
Task matcher = Task.Run(() =>
{
//Process each file making it as parallel as possible
Parallel.ForEach(contentList.GetConsumingEnumerable(), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 2 }, file =>
{
//Read in the whole file and make it lowercase
//This makes it so the Regex engine does not have
//to do it for each 175 patterns!
StreamReader stream = new StreamReader(File.OpenRead(file.FullName));
string inputString = stream.ReadToEnd();
stream.Close();
string inputStringLC = inputString.ToLowerInvariant();
//Run through all the patterns as parallel as possible
Parallel.ForEach(_Rules, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 2 }, rule =>
{
MatchCollection matches = null;
int matchCount = 0;
Stopwatch ruleTimer = Stopwatch.StartNew();
//Run the match for the rule and then get our count (does the actual match iteration)
try
{
//This does not work - Causes Deadlocks:
//matches = rule.Matches(inputStringLC);
//This works - No Deadlocks;
matches = rule.Matches2(inputStringLC);
//Process the regex by calling .Count()
if (matches == null) matchCount = 0;
else matchCount = matches.Count;
}
//Catch timeouts
catch (Exception ex)
{
//Log the error
string timeoutMessage = String.Format("****** Regex Timeout: {0} ===> {1} ===> {2}", ruleTimer.Elapsed, rule.Pattern, file.FullName);
Console.WriteLine(timeoutMessage);
matchCount = 0;
}
ruleTimer.Stop();
if (matchCount > 0)
{
cnt++;
//Iterate the matches and generate our match records
foreach (Match match in matches)
{
//Fill my result object
//...
//Add the result to the collection
results.Add(result);
}
}
});
});
});
//Wait until all are done.
Task.WaitAll(collector, matcher);
Console.WriteLine("Found {0:n0} files with {1:n0} matches", cnt, results.Count);
return results.ToList();
}
Обновление 2
Тест, который я проводил, не зашел в тупик, но когда он приблизился к концу, он, казалось, остановился, но я все еще мог взломать процесс с VS. Затем я понял, что в моем тесте не установлен тайм-аут, тогда как в коде, который я разместил (rule.Matches
и rule.Matches2
), это было сделано. С Тайм-аут, он блокируется. БЕЗ таймаута, это не так. Оба все еще работают в .Net Framework 4.6. Мне нужно время ожидания для регулярного выражения, потому что есть некоторые большие файлы, на которых останавливаются некоторые шаблоны.
Обновление 3:
Я играл со значениями тайм-аута, и, кажется, это какая-то комбинация запущенных потоков, исключений из тайм-аутов и значения тайм-аута, которое приводит к тупиковой блокировке движка Regex. Я не могу точно определить это, но тайм-аут> = 5 минут, кажется, помогает. В качестве временного исправления я могу установить значение 10 минут, но это не постоянное исправление!