AppDomain,处理异常

本文关键字:异常 处理 AppDomain | 更新日期: 2023-09-27 18:05:35

我正在开发一个由许多较小的插件/应用程序组成的大型应用程序。

它们不够大,不能成为一个完整的进程,但是太小,不能在一个进程下的线程中运行,同时我想让它基于插件。如果该插件有更新的版本,应该卸载、更新并重新启动。

在我寻找解决方案的过程中,我可以跨越AppDomain这个神奇的词,我引用:

"使用应用程序域来隔离可能导致故障的任务的过程。如果正在执行任务的AppDomain的状态变为不稳定时,可以卸载AppDomain而不影响进程。当流程必须长时间运行时,这一点非常重要重新启动。您还可以使用应用程序域来隔离任务不应该共享数据。"

这正是我想要的。然而,我想他们的"国家变得不稳定"与我的观点不同。我正在考虑一个问题,其中一个插件抛出异常,无论出于何种原因。我希望它能被捕获,通过电子邮件发送,卸载并重新启动(如果可能的话)。

所以我创建了一个应用程序,它会启动,查找文件夹中所有的。dll。检查dll是否包含插件。为该插件创建一个新的AppDomain,一旦加载完毕,它将启动每个插件。(其中每个插件可以由多个线程组成,彼此愉快地共存)。

所以我还在那里添加了一个超时,在5秒后触发抛出一个新的Exception();在AppDomain上增加UnhandledException事件来处理。但是,它捕获了它,并且在捕获之后,仍然"崩溃"了整个进程,包括所有额外的子应用域。

但是它在引言中明确指出"隔离"可能"降低流程"的任务。我是不是错过了什么重要的东西?我对这句话的看法错了吗?

AppDomain,处理异常

由于。net 2.0未处理的异常导致进程崩溃。从应用程序域中。UnhandledException事件文档:

此事件提供未捕获异常的通知。它允许应用程序将异常信息记录在系统之前默认处理程序将异常报告给用户,并且终止应用 .

AppDomain也是如此。FirstChanceException:

该事件只是一个通知。处理此事件不处理以任何方式影响后续异常处理。

你需要考虑如何处理异常,就像你在正常的应用程序中做的那样。仅仅使用AppDomains是没有用的。如果在给定的AppDomain内没有处理异常,它将在调用AppDomain时被重新抛出,直到它被处理或使进程崩溃。处理一些异常并且不让它们破坏您的进程是完全可以的。

AppDomain是程序集和内存的逻辑容器(不是线程的容器)。AppDomain的隔离意味着:
  • 在域A中创建的对象不能被域B直接访问(没有封送处理)。这允许卸载域A而不影响域b中的任何内容。当' owned '域被卸载时,这些对象将被自动删除。

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

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

使用AppDomain最常见的原因是当你运行不受信任的第三方代码。或者您有非托管代码并希望托管CLR或需要dll热插拔。我认为在CLR托管场景中,当第三方代码抛出未处理的异常时,您可以避免进程崩溃。

另外,与其滚动您自己的基础结构,您可能想要查看System。插件或MEF

两个未处理异常的问题。AppDomain只解决其中一个问题。你在和另一个人打交道。

先有好消息。处理异常时,必须恢复程序状态,就好像异常从未发生过一样。一切都必须回到代码运行之前的状态。通常有一堆catch和finally子句来撤消代码执行的状态变化。当然不是很简单的。但如果不处理异常则完全不可能。你不知道是什么发生了突变,也不知道如何恢复它。AppDomain沉着地处理了这个非常困难的问题。你卸载它,剩下的状态就消失了。不再有垃圾收集堆,不再有加载器堆(静态)。整个enchilada被重置为创建AppDomain之前的状态。

太好了。但还有一个问题也很难解决。您的程序被要求执行一项工作。线程开始做这项工作。但是它心脏病发作了。第一个大问题:线程是死的。如果您的程序开始时只有一个线程,那么这是一个非常坏的消息。没有线程剩余,程序终止。很好,AppDomain先卸载,但这真的没有什么区别,它无论如何都会被卸载的。

也是个大问题:完成这项工作真的很重要。它没有。这很重要,比如说,他的工作是平衡公司的损益表。这没有完成,必须有人来处理这个问题,因为平衡声明会让很多人非常不安。

如何解决这个问题?

只有几个选定的场景是可以接受的。服务器场景。有人要求它做某事,服务器报告"无法做到,请联系系统管理员"。ASP。. NET和SQL Server工作。他们使用AppDomains来保持服务器的稳定。并且有系统管理员来处理这些问题。你必须创建这样的支持系统,让AppDomains为你服务。

对于那些认为使用应用程序域主要是为了保证应用程序稳定性的人来说,只是在这个主题上添加一些额外的信息:

几年前,System.AddIn团队发表了一篇非常有趣的博客文章。使用AppDomain隔离检测外接程序故障。

说明只有进程外插件才能保证主机的稳定性。更具体地说:

从CLR v2.0开始,子线程上未处理的异常将现在导致整个过程被拆除,因此这是不可能的要使主机完全恢复。

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

他们还发表了另一篇博客文章,展示了如何在主机运行在另一台主机(IIS, WAS等)的情况下做到这一点。更多关于从托管外接程序记录未处理异常的信息。

虽然这两篇文章都是围绕System.AddIn展开的,但是对于那些试图提高插件感知应用程序稳定性的人来说,它们包含了有用的信息。

AppDomain更常用于能够卸载程序集(如您的建议)和控制启动参数,如。net访问级别,配置等。如果你真的想要"隔离",那么最好的选择总是一个worker进程;但是,这需要更多的工作。

我在几个项目中做了相当多的这样的事情。为了给出一个大致的描述,我们在一个托管的Windows LRPC库上使用Google ProtoBuffers (Jon Skeet的端口)来进行大多数通信。对于工作进程管理,我们严重依赖命名事件,我最近在这里发布了一个进程间事件库,就是为了这个目的。