Как изящно выгрузить дочерний домен AppDomain с запущенными потоками - PullRequest
7 голосов
/ 31 марта 2011

У меня есть служба, которая загружает дочерний домен приложения, а затем запускает поток, выполняющийся в нем.Ему нужен AppDomain, потому что он динамически генерирует и загружает некоторый код, и мне нужно иметь возможность перезапустить его, не убивая весь сервис.

Таким образом, в дочернем AppDomain работает поток, выполняющийся в цикле событий, он получаетсобытия, переданные ему через MarshalByRefObject, который помещает вещи в параллельную очередь.Я хочу остановить и выгрузить дочерний AppDomain и создать новый.

Я могу просто вызвать Unload на дочернем AppDomain, но это прервет все потоки и выдаст исключение ThrearAbortException.Как я могу изящно закрыть его?Если я установлю некоторый статический флаг в дочернем домене приложения с помощью MarshalByRefObject, то как главный процесс сможет ждать, пока не завершится его выгрузка?

У меня есть пример кода, который показывает, как его настройка и как я могуВызовите Unload, чтобы убить его, как я могу изменить это, чтобы разрешить постепенную выгрузку и никогда не иметь несколько дочерних доменов приложений?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;
using System.Security.Permissions;
using System.Reflection;
using System.Threading;

namespace TestAppDomains
{
    /// <summary>
    /// Calls to methods magically get transfered to the appdomain it was created in because it derives from MarshalByRefObject
    /// </summary>
    class MarshalProxy : MarshalByRefObject
    {
        public AppDomain GetProxyAppDomain()
        {
            return AppDomain.CurrentDomain;
        }

        public void SayHello()
        {
            Console.WriteLine("MarshalProxy in AD: {0}", AppDomain.CurrentDomain.FriendlyName);
        }

        public void RunLoop()
        {
            try
            {
                while (true)
                {
                    Console.WriteLine("RunLoop {0} in {1}", DateTime.Now.ToLongTimeString(), AppDomain.CurrentDomain.FriendlyName);
                    Thread.Sleep(1000);
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine("You killed me! {0}", ex);
                Thread.Sleep(200); //just to make sure the unload is really blocking until its done unloading
                // if the sleep is set to 2000 then you will get a CannotUnloadAppDomainException, Error while unloading appdomain. (Exception from HRESULT: 0x80131015) thrown from the .Unload call
            }
        }

        static int creationCount = 1;
        public static MarshalProxy RunInNewthreadAndAppDomain()
        {
            // Create the AppDomain and MarshalByRefObject
            var appDomainSetup = new AppDomainSetup()
            {
                ApplicationName = "Child AD",
                ShadowCopyFiles = "false",
                ApplicationBase = Environment.CurrentDirectory,
            };

            var childAppDomain = AppDomain.CreateDomain(
                "Child AD " + creationCount++,
                null,
                appDomainSetup,
                new PermissionSet(PermissionState.Unrestricted));

            var proxy = (MarshalProxy)childAppDomain.CreateInstanceAndUnwrap(
                typeof(MarshalProxy).Assembly.FullName,
                typeof(MarshalProxy).FullName,
                false,
                BindingFlags.Public | BindingFlags.Instance,
                null,
                new object[] { },
                null,
                null);

            Thread runnerThread = new Thread(proxy.RunLoop);
            runnerThread.Name = "MarshalProxy RunLoop";
            runnerThread.IsBackground = false;
            runnerThread.Start();

            return proxy;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("I am running in AD: {0}", AppDomain.CurrentDomain.FriendlyName);

            var proxy = MarshalProxy.RunInNewthreadAndAppDomain();
            proxy.SayHello();

            while (true)
            {
                Console.WriteLine("Press enter to kill and restart proxy");
                Console.WriteLine();
                Console.ReadLine();

                Console.WriteLine("Unloading");
                AppDomain.Unload(proxy.GetProxyAppDomain());
                Console.WriteLine("Done unloading");

                proxy = MarshalProxy.RunInNewthreadAndAppDomain();
            }
        }
    }
}

Ответы [ 2 ]

8 голосов
/ 23 июня 2011

Попробуйте следующее

runnerThread.IsBackground = true;

И, да, не происходит постепенной выгрузки AppDomain, если вы сначала не остановили потоки.

2 голосов
/ 23 июня 2011

Ситуация, по сути, такая же, как если бы два домена приложений были отдельными процессами, поэтому вам нужно использовать некоторую форму IPC.Один из вариантов - передать дескриптор события в дочерний домен AppDomain при запросе остановки цикла.Цикл может сигнализировать о событии перед выходом.Подождите, пока событие не даст циклу закончиться.Если время истекло, вы можете выполнить грубую разгрузку.

...