AppDomain,处理exception

我正在开发一个大型应用程序,它包含许多较小的插件/应用程序。

它们不够大,不足以成为一个完整的进程,但是在一个进程中,它太小而无法在一个线程中运行,而且我希望它基于一个插件基础。 如果该插件的较新版本可用,则应该卸载,更新并重新启动。

在我寻找解决方案期间,我可以使用魔术字AppDomain,我引用:

“使用应用程序域来隔离可能导致进程崩溃的任务。如果正在执行任务的AppDomain状态变得不稳定,则可以卸载AppDomain而不会影响进程。当进程必须长时间运行而不重新启动时,这很重要您还可以使用应用程序域来隔离不应共享数据的任务。“

因此,这正是我想要的。 但是,我猜他们的“状态变得不稳定”是一种不同于我的观点。 我正在考虑一个问题,其中一个插件抛出exception,无论出于何种原因。 我想抓住,通过电子邮件发送,卸载并重启(如果可能的话)。

所以我创建了一个启动的应用程序,在其文件夹中查找所有.dll文件。 检查dll是否包含插件。 为该插件创建一个新的AppDomain,一旦加载了所有内容,它将启动每个插件。 (每个插件可以由多个线程组成,与其他插件紧密共存)。

所以我还在那里添加了一个超时,在5秒之后触发抛出一个新的Exception(); 在AppDomain上添加了UnhandledException事件来处理它。 但是,它抓住了它,并且在继续之后,仍然“崩溃”整个过程,包括所有额外的孩子 – AppDomains。

但它在报价中明确指出要隔离“可能”扼杀一个过程的任务。 我错过了一些重要的东西吗? 我对报价的看法是错误的吗?

由于.NET 2.0 未处理的exception导致进程崩溃 。 从AppDomain.UnhandledException事件文档:

此事件提供未捕获exception的通知。 它允许应用程序在系统默认处理程序向用户报告exception并终止应用程序之前记录有关exception的信息。

AppDomain.FirstChanceException也是如此 :

此事件仅是通知 。 处理此事件不会以任何方式处理exception或影响后续exception处理。

您需要考虑如何处理exception,就像在普通应用程序中执行exception一样。 仅使用AppDomains无济于事。 如果在给定的AppDomain中没有处理exception,它将在调用AppDomain时重新抛出,直到它被处理或崩溃进程。 处理一些exception非常好,不要让它们崩溃你的过程。

AppDomain是程序集和内存的逻辑容器(不适用于线程)。 AppDomain的隔离意味着:

  • 域B中创建的对象不能由域B直接访问(没有编组)。 这允许在不影响域B中的任何内容的情况下卸载域A.当“拥有”域被卸载时,这些对象将被自动删除。

  • 可以使用AppDomain自动卸载程序集。 这是您可以从进程中卸载托管dll的唯一方法。 这对DLL热交换很有用。

  • AppDomain安全权限和配置可以与其他AppDomain隔离。 加载不受信任的第三方代码时,这可能会有所帮助。 它还允许您覆盖程序集的加载方式(版本绑定,卷影复制等)。

使用AppDomain的最常见原因是您运行不受信任的第三方代码。 或者你有非托管代码,想要托管CLR或需要dll热交换。 我认为在CLR托管方案中 ,当第三方代码抛出未处理的exception时,您可以防止进程崩溃。

此外,您可能希望查看System.Addin或MEF,而不是滚动自己的基础结构。

未处理的exception有两个问题。 AppDomain只解决其中一个问题。 你正试图处理另一个。

好消息首先。 处理exception时,必须恢复程序状态,就好像exception从未发生过一样。 一切都必须重新回到代码运行前的状态。 你通常有一堆catch和finally子句来撤消代码执行的状态变化。 当然没有什么比这更简单了。 但如果exception未得到处理,则完全不可能。 你不知道究竟是什么变异以及如何恢复它。 AppDomain使用aplomb来处理这个非常棘手的问题。 你卸下它,剩下的状态就消失了。 不再有垃圾收集堆,没有更多的加载器堆(静态)。 创建AppDomain 之前 ,整个enchilada会重置为状态。

那很棒。 但是还有一个问题很难处理。 你的程序被要求执行一项工作。 该线程开始做这项工作。 但它遭受了心脏病发作。 第一大问题:线程已经死了。 如果你的程序只有一个线程可以开始,这是个坏消息。 没有线程,程序终止。 很高兴AppDomain首先卸载,但它确实没有任何区别,无论如何它都会被卸载。

也是一个大问题:完成这项工作真的非常重要。 它没有。 重要的是,这项工作是平衡公司损益表。 这没有完成, 有人将不得不照顾这一点,因为不平衡声明会让很多人非常沮丧。

你是怎么解决的?

只有少数选定的场景可以接受。 服务器场景。 有人要求它做某事,服务器报告“无法做到,请联系系统管理员”。 ASP.NET和SQL Server的工作方式。 他们使用AppDomains来保持服务器稳定。 并让系统管理员来处理问题。 您必须创建这种支持系统才能使AppDomains为您服务。

只需为使用应用程序域考虑(自己在那里)的任何人添加一些关于主题的额外信息,主要是为了保证应用程序的稳定性:

几年前, System.AddIn团队发布了一篇非常有趣的博客文章。 使用AppDomain隔离来检测加载项失败 。

它解释了只有进程外加载项才能保证主机的稳定性。 进一步来说:

从子线程上的CLR v2.0未处理的exception开始,现在将导致整个进程被拆除,因此主机无法从中完全恢复。

所以他们建议订阅AppDomain.UnhandledException,并在应用程序崩溃之前,存储有关导致此exception的人的信息(日志,数据库等)。 然后,下次启动应用程序时,请使用此信息来保护您的应用程序。 也许您没有加载加载项,或者您通知用户并让他/她决定。 (Microsoft Office应用程序遵循此方法并禁用了导致主机崩溃的插件。然后您必须自己重新启用它们。)

他们还发布了另一篇博客文章,展示了即使在主机在另一台主机(IIS,WAS等)中运行的情况下如何执行此操作。 有关从托管加载项记录UnhandledExeptions的更多信息 。

虽然这两篇文章都以System.AddIn为中心,但它们包含了任何试图提高其插件感知应用程序稳定性的人的有用信息。

AppDomain更常用于卸载程序集(如你的建议)和控制启动参数,如.NET访问级别,配置等。如果你真的想要’隔离’那么最好的选择总是工作进程; 但是,它还有很多工作要做。

我在几个项目中做了相当多的工作。 为了给出一个广泛的图片,我们使用Google ProtoBuffers ( Jon Skeet的端口 )通过托管的Windows LRPC库进行大多数通信。 对于工作流程管理,我们在很大程度上依赖于命名事件,我最近在这里发布了一个进程间事件库 。