Unload an AppDomain while using IDisposable

笑着哭i 提交于 2020-06-28 07:08:10

问题


I have a marshalable class that contains a factory method. The factory method can be used to instantiate the class in a test AppDomain. I'm trying to understand whether I can use the class with the using( ... ) dispose pattern.

The principle concern for me is whether the test AppDomain is Unloaded -- and when. I've added a static flag to the class that's set when an instance method is invoked. If the appdomain is not unloaded, then I believe this flag should retain it's setting in subsequent invocations.

A sample class and test console app would look like this:

using System;
using System.Reflection;
using System.Threading;

namespace AppDomainInDispose
{
    public class TestClass : MarshalByRefObject, IDisposable
    {
        public void Run()
        {
            Console.WriteLine("Hello from {0}", Thread.GetDomain().FriendlyName);
            if (_flag)
                Console.WriteLine("Flagged!");
            else
                _flag = true;
        }

        private static bool _flag = false;

        public static TestClass InstantiateInTestDomain()
        {
            var callingDomain = Thread.GetDomain();
            var setup = new AppDomainSetup() { ApplicationBase = callingDomain.SetupInformation.ApplicationBase };
            _domain = AppDomain.CreateDomain("test-domain", null, setup);
            _domain.DomainUnload += _domain_DomainUnload;

            var assembly = Assembly.GetAssembly(typeof(TestClass)).CodeBase;
            var proxy = _domain.CreateInstanceFromAndUnwrap(assembly, "AppDomainInDispose.TestClass") as TestClass;

            return proxy;
        }

        static void _domain_DomainUnload(object sender, EventArgs e)
        {
            Console.WriteLine("Unloading");
        }

        public static AppDomain _domain = null;

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~TestClass()
        {
            Dispose(false);
        }

        private void Dispose(bool disposing)
        {
            if(disposing)
            {
                //AppDomain.Unload(_domain);    // can't, as I'm in the AppDomain
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using(var testClass = TestClass.InstantiateInTestDomain())
            {
                testClass.Run();
            }
            using (var testClass = TestClass.InstantiateInTestDomain())
            {
                testClass.Run();    // if the appdomain hasn't been unloaded then the static flag will still be set
            }

            Console.ReadKey();
        }
    }
}

It seems that in this limited test the AppDomain is being unloaded -- but I'm not sure where. Could someone explain what's going on with the AppDomain? Is this approach a very bad idea?

EDIT: Looks like you can have multiple AppDomains sharing the same name, which I didn't realise. Also, the DomainUnload event isn't fired, so I might assume that the domain is not being unloaded. That, or the event is not fired in some circumstances (perhaps while the hosting process is shutting down).


回答1:


Hmmm.. it is getting a bit complicated. If you want to be sure that the app domain does get unloaded after the using block end, below is one such implementation. I have separated the 2 concerns here "create app domain and handling of app domain" and "the Type itself that is being loaded in app domain". (All this is in main prog class)

public class GenericDisposable<T> : IDisposable
{
    public Action Dispose { get; set; }
    public T Object { get; set; }
    void IDisposable.Dispose()
    {
        Dispose();
    }

}
public static GenericDisposable<T> CreateDomainWithType<T>()
{
    var appDomain = AppDomain.CreateDomain("test-domain");
    var inst = appDomain.CreateInstanceAndUnwrap(typeof(T).Assembly.FullName, typeof(T).FullName);
    appDomain.DomainUnload += (a, b) => Console.WriteLine("Unloaded");
    return new GenericDisposable<T>() { Dispose = () => AppDomain.Unload(appDomain), Object = (T)inst };
}
public class User : MarshalByRefObject
{
    public void Sayhello()
    {
        Console.WriteLine("Hello from User");
    }
}

//Usage              
static void Main()
{
    using (var wrap = CreateDomainWithType<User>())
    {
        wrap.Object.Sayhello();
    }
    Console.Read();
}



回答2:


Necromancing.
The disposable action did not work for me, since the action delegate is not serializable.
So here's how I did it:

Protected Overridable Sub Dispose(disposing As Boolean)
    If Not disposedValue Then
        If disposing Then
            ' TODO: verwalteten Zustand (verwaltete Objekte) entsorgen.

            If Me.LoadContext IsNot Nothing Then
                Me.LoadContext.Unload()
                Me.LoadContext = Nothing
            End If

            If Me.Stream IsNot Nothing Then
                Me.Stream.Dispose()
                Me.Stream = Nothing
            End If

            'If Parameters.m_parameterValues IsNot Nothing Then
            '    Parameters.m_parameterValues.Clear()
            '    Parameters.m_parameterValues = Nothing
            'End If

            If Me.Domain IsNot Nothing Then
                ' https://stackoverflow.com/questions/7793074/unload-an-appdomain-while-using-idisposable
                Dim thread As System.Threading.Thread = New System.Threading.Thread(
                  Sub(domain As System.AppDomain)
                      Try
                          ' System.Threading.Thread.Sleep(1000)
                          System.AppDomain.Unload(domain)
                      Catch ex As System.Threading.ThreadAbortException
                          ' System.Console.WriteLine(ex.Message)
                          System.GC.Collect()
                          System.GC.WaitForPendingFinalizers()
                      End Try
                  End Sub
                )
                thread.IsBackground = True
                thread.Start(Me.Domain)
                Me.Domain = Nothing
            End If

            System.GC.Collect()
            System.GC.WaitForPendingFinalizers()
        End If

        ' TODO: nicht verwaltete Ressourcen (nicht verwaltete Objekte) freigeben und Finalize() weiter unten überschreiben.
        ' TODO: grosse Felder auf Null setzen.
    End If
    disposedValue = True
End Sub

The secret is doing it in a new thread:

Dim thread As System.Threading.Thread = New System.Threading.Thread(
      Sub(domain As System.AppDomain)
          Try
              System.AppDomain.Unload(domain)
          Catch ex As System.Threading.ThreadAbortException
              ' System.Console.WriteLine(ex.Message)
              System.GC.Collect()
              System.GC.WaitForPendingFinalizers()
          End Try
      End Sub
)
thread.IsBackground = True
thread.Start(Me.Domain)

Then the thread gets killed, but the code continues to run.



来源:https://stackoverflow.com/questions/7793074/unload-an-appdomain-while-using-idisposable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!