Можно ли предотвратить необработанные исключения в дочерних доменах приложений от сбоя основного процесса? - PullRequest
8 голосов
/ 29 июня 2011

Я пишу небольшую библиотеку плагинов, которая использует домены приложений для изоляции плагинов с помощью .Net Framework 4.0.Следовательно, код, который входит в каждый плагин, находится вне моего контроля.Когда в одном из плагинов возникает необработанное исключение, я заметил, что результаты выглядят как смешанная сумка.Они выглядят следующим образом.

Когда необработанное исключение возникает в главном потоке плагина, основное подключаемое приложение, вызывающее метод execute плагина, способно его аккуратно перехватить и обработать.Там нет проблем.Тем не менее,

  1. Если плагин запускает цикл обработки сообщений для приложения на основе WinForms в методе Execute плагина и в приложении WinForm (то есть в форме) возникает необработанное исключение, то подключаемый модульПриложение может перехватить исключение, только если оно запускается из отладчика Visual Studio.В противном случае (при вызове вне VS) основное подключаемое приложение аварийно завершает работу вместе с подключаемыми модулями.

  2. Если необработанное исключение создается в отдельном потоке, созданном методом 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 Ответ

1 голос
/ 30 июня 2011

Для обработки исключений WinForms. Вы можете установить «ловушку» для таких исключений ThreadException в классе PluginBase:

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);
            };
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...