5

我有麻烦了。我正在开发的应用程序的用户之一偶尔但经常遇到应用程序挂起。

发生这种情况时,我们会在机器的事件日志中找到一个来源为“应用程序挂起”的条目,其中包含信息性消息“挂起应用程序 [我的应用程序],版本 [正确版本],挂起模块挂起应用程序,版本 0.0.0.0,挂起地址 0x00000000。”

我正在记录我的应用程序抛出的所有未处理的异常,发生这种情况时我的日志文件中没有任何条目。

我目前的工作假设是,在应用程序调用不安全的遗留 API 期间会发生这种挂起。这不会让我感到惊讶;我使用这个 API 已经很多年了,虽然我以前没见过它挂起,但它确实是很糟糕的代码。此外,用户报告该程序似乎随机挂起。我不认为这是真的。并不是我不相信她,而是与遗留 API 对话的代码在 BackgroundWorker 调用的方法中运行。如果后台线程使应用程序挂起,这在用户看来很可能是随机发生的。

所以,我有两个问题,一个具体的,一个一般的。

具体问题:我希望如果在非 UI 线程上运行的方法挂起,它只会杀死线程。它真的会杀死整个应用程序吗?

一般问题:

我已经记录了所有未处理的异常。我的程序已经设置为使用跟踪(尽管我需要添加检测代码来跟踪可疑方法中的活动)。还有其他我应该做的事情吗?当 .NET 应用程序挂起时,是否有允许进行某种崩溃后分析的诊断工具?.NET 框架中是否有我可以调用的机制来捕获更多(和更有用的)数据?

编辑: 仔细检查我的代码,我记得它对 BackgroundWorker 的所有使用都是通过我实现的实用程序类来包装异常处理程序中调用的方法。此处理程序记录异常,然后将其作为实用程序对象的属性返回。UI 线程中的完成事件处理程序重新抛出异常(不太理想,因为我丢失了调用堆栈,但它已经被记录了),导致 UI 的主要异常处理程序将异常报告给消息框,然后终止应用程序。

由于这些都没有发生,我非常有信心在后台线程中没有抛出异常。好吧,无论如何,没有 .NET 例外。

进一步跟进:

幸运的是,我现在已经从用户那里获得了足够的数据,可以确定遗留 API 中没有发生挂起。这意味着这显然是我做错了,这意味着我可以修复它,所以,赢了。这也意味着我可以通过跟踪来隔离问题,这是另一个胜利。我对这个问题的答案感到非常高兴;我什至更高兴我可能不需要它们来解决这个问题。

另外:PostSharp 非常出色。如果您需要向现有应用程序添加检测代码,您几乎可以肯定应该使用它。

4

7 回答 7

4

在回答您的具体问题时,当后台/工作线程阻塞或挂起时,对应用程序其余部分的影响在很大程度上取决于应用程序中线程之间发生的同步。没有特别的理由表明它一定会挂起整个应用程序,但它完全有可能会挂起。

诊断此问题的一种可能方法是在进程挂起时生成进程转储(假设有人在附近注意到它何时发生)。这将使用来自 dbghelp.dll 的MiniDumpWriteDump来完成。编写一个可以转储进程(基于其 pid)的简单工具相当简单,该工具可以提供给遇到问题的客户。由于这是一个托管应用程序,因此最好使用完整的内存转储 (MiniDumpWithFullMemory),但普通转储仍应包含一些有用的信息。获得转储后,您可以使用 windbg 或您选择的事后调试器来查看可能发生的情况。

如果你走这条路,这篇 msdn 文章是托管转储调试的一个很好的起点。

于 2008-10-14T19:07:49.897 回答
1

我建议围绕您认为是问题根源的呼叫添加更详细的日志记录。

如果您使用的是 Vista,您可以使用新的 Vista API 在您的应用程序崩溃时让 Windows 调用您的代码。当您看到诸如 Office/IE 之类的 MS 产品说它们“正在尝试恢复您的数据”时,就会发生这种情况。

于 2008-10-14T19:08:39.693 回答
0

想法 1)步入 .net 框架代码(来自我工作的 KB):

如果你已经安装了 VS2008 SP1,你需要做的就是去工具 -> 选项 -> 调试

  1. 取消选中仅启用我的代码
  2. 选中启用 .NET Framework 源步进
  3. 检查启用源服务器支持
  4. 在 Debugging -> Symbols 下,添加http://referencesource.microsoft.com/symbols的新位置

现在,当调试调用堆栈中的框架代码变灰时,只需右键单击调用行并选择加载符号。

想法2)设置远程调试http://msdn.microsoft.com/en-us/library/y7f5zaaa.aspx

于 2008-10-14T19:07:47.383 回答
0

如果您控制的线程上有未处理的执行,它将关闭您的整个应用程序。一旦线程死亡,就没有办法“处理”这个问题。您可能想了解如何将APM 与代表一起使用。这为其他线程上抛出的异常提供了一层保护,因为当您调用 EndInvoke() 时会捕获并提出异常。

至于你还能做什么,我赞同查理的回答。

于 2008-10-14T19:10:05.353 回答
0

我建议附加 WinDbg(是的,其中一个核心的东西)并使用 SOS(罢工之子)和 SOSEx 来分析死锁(!dlk)或手动检查同步块(!syncblk)以找到相互等待的锁。

于 2008-10-14T21:27:39.723 回答
0

如果可能,将后台工作线程替换为SafeThread并查看是否捕获了可疑异常。如果不是,则抛出的异常不是 CLR 异常,您可能无法从“纯”.NET 代码中处理它[尽管 C++ 中的 SEH 可能有效]

编辑:好的,不是这样。也许这个这个可能会有所帮助。祝你好运!

于 2008-10-14T19:14:07.310 回答
0

罗伯特,如果所有这些解决方案都失败了,并且您仍然认为遗留 API 是罪魁祸首,那么答案可能是将遗留 API 沙箱化到它自己的 AppDomain 或进程中。

.NET 3.5 框架使用 System.AddIn API 使这很容易做到。

于 2008-10-14T20:28:32.743 回答