【问题标题】:Kicking off Tasks inside of ForEach debugs to NullReferenceException启动 ForEach 内的任务调试 NullReferenceException
【发布时间】: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 只包含无空值吗?
  • _objectifiableMatchesConcurrentBag&lt;string&gt; _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


【解决方案1】:

看到视频后,我看到您将断点置于变量 (1) 的范围之外。

您的代码绝对正确,但我想知道您为什么要将代码并行化两次。在我看来,您不必这样做,但这是一个不同的话题。

在关键字变量初始化之后放置断点时,您将获得一个值 (2),如下所示:

【讨论】:

    猜你喜欢
    • 2020-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-10
    • 1970-01-01
    • 1970-01-01
    • 2020-09-21
    • 1970-01-01
    相关资源
    最近更新 更多