【发布时间】:2021-11-29 09:27:05
【问题描述】:
我正在启动一些任务来匹配长字符串中的正则表达式。
我的任务列表如下所示:
var splittingTasks = new List<Task>();
foreach (var keyword in _general.Keywords)
{
splittingTasks.Add(Task.Run(() => SplitMatches(keyword)));
}
await Task.WhenAll(splittingTasks);
SplitMatches 方法如下所示:
ConcurrentBag<string> _objectifiableMatches = new();
//...
public void SplitMatches(string keyword)
{
string patternObject = $@"\/begin {keyword}[\s\S]+?\/end {keyword}";
Regex regexObject = new(patternObject);
MatchCollection Matches = regexObject.Matches(_content);
Parallel.ForEach(Matches, m =>
{
var replacedQuotes = m.Value.Replace($"\"", "'");
_objectifiableMatches.Add(replacedQuotes);
});
}
将await Task.WhenAll(splittingTasks); 结果调试为NullReferenceException for keyword: 'keyword' threw an exception of type 'System.NullReferenceException'。不过结果还是符合预期的。
我读过this post,它说 for 循环中的 lambda 表达式可能会导致问题,因为不会立即评估委托。
但即使在 foreach 循环中复制变量后,我仍然会遇到同样的错误。
foreach (var kw in _general.Keywords)
{
string keyword = kw;
splittingTasks.Add(Task.Run(() => SplitMatches(keyword)));
}
你有什么建议吗?
编辑:
我的关键字列表_general.Keywords 是通过选项模式从appsettings.json 文件导入的。但是,尝试以下方法会产生相同的调试错误:
List<string> keywords = new()
{
"keyword1",
"keyword2",
"keyword3",
"keyword4",
"keyword5"
};
foreach (var kw in keywords)
{
string keyword = kw;
splittingTasks.Add(Task.Run(() => SplitMatches(keyword)));
}
编辑2:
如果我错了,请纠正我,但我认为错误仅来自调试。由于任务存在于循环之外,但keyword 不存在。因此调试器尝试访问未在作用域中声明的关键字的最后一个值。在foreach 之外声明变量keyword 不会产生任何错误,但结果却是完全错误的。我可以忽略这个错误吗?
EDIT3:
foreach (var task in splittingTasks)
{
Console.WriteLine(task.IsCompletedSuccessfully.ToString());
}
为所有任务返回true。而且没有比预期更多的任务! (对于第一次编辑中提到的列表,它将是 5 个任务)
编辑4:
I uploaded a short video displaying the problem of Visual Studio
在我看来,即使在 for 循环中声明变量 keyword 之前,VS 也在评估它。我不明白为什么,我什至找不到抛出这个错误的方法。
编辑5:
你可以找到一个最小的可重现示例here
在visual studio中复制粘贴并尝试调试方法GetKeywordMatchesAsync()
【问题讨论】:
-
你确定
_general只包含无空值吗? -
_objectifiableMatches是ConcurrentBag<string> _objectifiableMatches = new();。关于启动这样的任务:我对 C# 中的 async/await 编程非常陌生,这就是 Tim Corey 在他的 youtube 视频中所做的方式 :-) 如果您指出我的“更好”方式,我们将不胜感激 -
@Dai 我在这里上传了一个视频:drive.google.com/file/d/1nuISdp8HNN3ZQctmUaZBtTEqHcqYxs8-/… 请告诉我它是否适用于你。有趣的是,变量关键字甚至在进入 for 循环之前就被评估了,我无法理解为什么会这样..
-
你在担心你不应该担心的事情:)。您正在使用的 lambda 表达式是在后台使用一些辅助类编译的。您的变量关键字是这些类的成员。调试器隐藏了这种复杂性,并在创建/处置这些帮助类之前和之后运行到 NullReferenceException。单个变量永远不会抛出 NullReferenceException,不管它有什么值。使用 ILSpy 打开您的程序集,切换回一些语言版本并查看您的方法以了解您的代码是如何编译的。
-
@TheodorZoulias 非常感谢您的建议。过度平行化是我迄今为止从未想过的事情。您的链接对我帮助很大,由于我没有指定任何程度的并行化,我删除了任何额外的并行语句。但是,我看不出如何将 PLINQ 与 Tasks 结合使用来消除并发收集的使用,因为每个任务都在编写相同的变量。您还可以详细说明为什么并发集合“不好”吗?
标签: c# debugging task nullreferenceexception parallel.foreach