是否可以防止子 AppDomain 中的未处理异常导致主进程崩溃?

Posted

技术标签:

【中文标题】是否可以防止子 AppDomain 中的未处理异常导致主进程崩溃?【英文标题】:Can Unhandled Exceptions in Child AppDomains be prevented from crashing the main process? 【发布时间】:2011-06-29 12:46:40 【问题描述】:

我正在编写一个小型插件库,它使用应用程序域来隔离使用 .Net 框架 4.0 的插件。因此,每个插件中的代码都超出了我的控制范围。当其中一个插件引发未处理的异常时,我观察到结果好坏参半。它们如下。

当插件的主线程中抛出未处理的异常时,调用插件的执行方法的主可插拔应用程序能够干净地捕获并处理它。那里没有问题。然而,

    如果插件在插件的 Execute 方法中为基于 WinForms 的应用程序启动消息循环,并且在 WinForm 应用程序中(即在表单中)抛出未处理的异常,则可插入应用程序只能捕获异常,如果它从 Visual Studio 调试器内部运行。否则(在 VS 外部调用时)主可插拔应用程序会与插件一起崩溃。

    如果未处理的异常是在插件的 Execute 方法生成的单独线程中引发的,则可插入应用程序没有机会捕获异常并崩溃。

我在下面的链接中创建了一个简单的 VS 2010 项目来模拟这种行为。 http://www.mediafire.com/file/1af3q7tzl68cx1p/PluginExceptionTest.zip

其中,可插拔应用程序中的主要方法如下所示

namespace PluginExceptionTest

    class Program
    
        static void Main(string[] args)
        
            Console.WriteLine("Press enter to load plugin");
            Console.ReadLine();

            Assembly entryAsm = Assembly.GetEntryAssembly();
            string assemblyFileName = Path.Combine(Path.GetDirectoryName(entryAsm.Location), "EvilPlugin.exe");


            AppDomainSetup domainSetup = new AppDomainSetup();
            AppDomain domain = AppDomain.CreateDomain("PluginDomain", null, domainSetup);
            PluginBase plugin = (PluginBase)domain.CreateInstanceFromAndUnwrap(assemblyFileName, "EvilPlugin.Plugin");

            Console.WriteLine("Plugin Loaded.");

            //SCENARIO 1: WinForms based plugin
            Console.WriteLine("Press Enter to execute winforms plugin. (Remember to click on the button in the form to raise exception)");
            Console.ReadLine();

            try
            
                plugin.ExecuteWinApp();
            
            catch (Exception)
            
                //The exception is caught and this gets executed only when running in visual studio debugger. Else application exits. Why?
                Console.WriteLine("WinForms plugin exception caught. However same does not happen when run out of visual studio debugger. WHY?");
            

            //SCENARIO 2: WinForms based plugin
            Console.WriteLine("Press Enter to execute threading plugin, wait for 3 seconds and the app will exit. How to prevent app from exiting due to this?");
            Console.ReadLine();
            try
            
                plugin.ExecuteThread();
            
            catch (Exception)
            
                //This never gets executed as the exception is never caught. Application exits. Why?
                Console.WriteLine("WinForms plugin exception caught");
            

            Console.ReadLine();
        
    

这是插件项目的代码。它继承自上述可插拔应用项目中的 PluginBase 类。

namespace EvilPlugin

    public class Plugin:PluginBase
    
        public Plugin():base()
        

        

        public override void ExecuteWinApp()
                    
            Application.Run(new Form1());            
        

        public override void ExecuteThread()
        
            Thread t = new Thread(new ThreadStart(RaiseEx));           
            t.Start();
        

        private void RaiseEx()
        
            Thread.Sleep(3000);
            throw new Exception("Another Evil Exception in a seperate thread");
        
    

最后这是插件中表单的代码

namespace EvilPlugin

    public partial class Form1 : Form
    
        public Form1()
        
            InitializeComponent();
        

        private void btnException_Click(object sender, EventArgs e)
        
            throw new Exception("Evil Exception");
        
    

如何防止主进程由于上述两种情况(1 和 2)而退出?

提前致谢。

【问题讨论】:

【参考方案1】:

用于处理 WinForms 异常。您可以在 PluginBase 类中为此类 ThreadExceptions 设置“陷阱”:

public abstract class PluginBase:MarshalByRefObject

    protected PluginBase()
    
        System.Windows.Forms.Application.ThreadException +=
            (o, args) =>
            
                throw new Exception("UntrappedThread Exception:" + args.Exception);
            ;

    

    public abstract void ExecuteWinApp();
    public abstract void ExecuteThread();

默认情况下,它会显示“异常窗口”。但是如果提供的话,会得到这个处理程序。

至于从其他线程捕获异常。没有办法。您能做的最好的事情就是收到有关抛出异常的通知。

    AppDomain domain = AppDomain.CreateDomain("PluginDomain", null, domainSetup);
    domain.UnhandledException +=
        (o, eventArgs) =>
            
                Console.WriteLine("Exception was caught from other AppDomain: " + eventArgs.ExceptionObject);
                Console.WriteLine("CLR is terminating?: " + eventArgs.IsTerminating);
            ;

【讨论】:

所以看起来 AppDomain 方法不是插件库的方法,因为我不希望我的主应用程序崩溃,因为插件在它产生的线程内导致异常。回到我猜想将它包装在一个过程中的旧方式。当 msdn 说 AppDomains 提供了一种隔离程序集的方法时,它并不是 100% 正确的。 AppDomain 隔离数据,而不是执行。 我想知道是否有某种方法可以防止新的 AppDomain 像上面的 ExecuteThread 方法那样产生额外的线程... 说真的,我也会对这样的解决方案感兴趣,其中子 AppDomain 仅限于单个线程。

以上是关于是否可以防止子 AppDomain 中的未处理异常导致主进程崩溃?的主要内容,如果未能解决你的问题,请参考以下文章

C# 截获某个域中未捕获的异常

AppDomain.UnhandledException 自动重新抛出已处理的异常

“创建后”触发器中的未处理异常阻止创建

在 ASP.NET Web Api 中捕获所有未处理的异常

当我加载的另一个 AppDomain 引发未处理的异常时,我可以隔离我当前的 AppDomain 以防被拆除吗?

防止 WCF 服务 AppDomain 被卸载