如何防止或恢复工作线程上的堆栈溢出?

时间:2012-08-02 19:07:05

标签: c# .net multithreading stack-overflow

我遇到过一种情况,根据minidump,某些文件在递归下降解析器中导致堆栈溢出。不幸的是,我无法得到一个文件的例子,这样做是为了重现这个问题(客户端有保密问题),这让我在诊断真正的问题时有点麻烦。

显然解析器需要注意,但现在我的首要任务是保持程序运行。作为权宜之计,我该怎样做才能阻止整个计划的落实?

我的第一选择是找到一些方法来预测我在堆栈上的空间不足以便我可以在溢出发生之前优雅地中止解析器。无法解析文件是可以接受的选项。第二种选择是让它发生,捕获错误并记录它,然后继续其余的数据。

解析发生在Parallel.ForEach()循环中。如果有帮助,我愿意将其换成其他方法。

编辑:如果我能够获得当前线程堆栈的大小以及堆栈指针的位置,那么真正的杀手是什么。这可能吗?

编辑2:我终于设法从某人那里取出一个示例文件并将错误记录在调试器中。事实证明,根本不是属于我们的代码 - 例外情况发生在HtmlAgilityPack的某个地方。所以看起来我将不得不尝试找到一个完全不同的方法。

2 个答案:

答案 0 :(得分:3)

默认情况下,桌面CLR上的堆栈限制为1 MB,但您can increase it

您可以使用continuation passing style来使用堆而不是堆栈。

在C#5.0中,编译器提供了异步机制来自动执行此过程。我没有尝试使用最新版本。正如Alex所提到的,C#中不支持尾调用优化,这可能足以成为采用F#解析问题的理由。这是some material on lexing and parsing with F#. YMMV,如this article.

中所示

您还需要使用图形周期检测功能,使您的程序在presence of bad inputs中保持稳定。

作为收集更多信息的一种方法,您可以通过累加器整数来跟踪调用堆栈的深度。这不会直接转换为所述调用堆栈所消耗的内存,但它为您提供了一个大致的想法。例如,当该数字大于某个用户可配置或预定义的阈值时,您可以抛出并捕获自己的异常。

public void Recursive(int acc)
{
    if (acc > myLimit)
       throw new MyOverflowException(acc); 

    Recursive(acc+1);
}

然后在呼叫现场:

try { Recursive(0); } catch (MyOverflowException) { /* handle it*/ }

根据要求,我会将您链接到Eric Lippert在this very topic.

上的精彩博客

答案 1 :(得分:0)

由于SOE引起的线程崩溃将导致整个过程失败,而且你无能为力。

作为一种恢复措施,您可以将解析器作为单独的进程启动,并设置IPC机制与子进程通信。这样,子进程可以自由死亡而不会影响主进程。