Processo Di Sostituzione.Iniziare con Appdomain

Sfondo

Ho un servizio di Windows che utilizza vari Dll di terze parti per eseguire lavori su file PDF. Queste operazioni possono usare un po ‘ di risorse di sistema, e, occasionalmente, sembrano soffrire di perdite di memoria quando si verificano errori. Le Dll sono gestiti wrapper intorno altri Dll non gestito.

Soluzione Corrente

Sto già mitigare questo problema, in un caso, avvolgendo una chiamata a una delle Dll in una console dedicata app e chiamata app tramite Processo.Start(). Se l’operazione non riesce e non ci sono perdite di memoria o inediti handle di file, non importa. Il processo termina e il sistema operativo consente di recuperare le maniglie.

Vorrei applicare la stessa logica per gli altri luoghi nella mia app che utilizzano queste Dll. Tuttavia, io non sono terribilmente eccitato circa l’aggiunta di più console di progetti per la mia soluzione, e la scrittura ancora più caldaia-piastra di codice che chiama Processo.Start() e analizza l’output della console app.

Nuova Soluzione

Un elegante alternativa alle console dedicata apps e di Processo.Start() sembra essere l’uso di Dominare, come questo: http://blogs.geekdojo.net/richard/archive/2003/12/10/428.aspx.

Ho implementato un codice simile nella mia applicazione, ma i test non sono stati promettenti. Ho deciso di creare un FileStream in un file di prova in un AppDomain separato, ma non smaltire esso. Ho quindi il tentativo di creare un altro FileStream nel dominio principale, e non riesce a causa di inedito, di blocco dei file.

È interessante notare che, l’aggiunta di un vuoto DomainUnload evento per il lavoratore di dominio rende l’unità di test. Indipendentemente da ciò, temo che forse la creazione di “lavoratore” Appdomain non risolvere il mio problema.

Pensieri?

Il Codice

///<summary>
///Executes a method in a separate AppDomain.  This should serve as a simple replacement
///of running code in a separate process via a console app.
///</summary>
public T RunInAppDomain<T>( Func<T> func )
{
    AppDomain domain = AppDomain.CreateDomain ( "Delegate Executor " + func.GetHashCode (), null,
        new AppDomainSetup { ApplicationBase = Environment.CurrentDirectory } );

    domain.DomainUnload += ( sender, e ) =>
    {
        //this empty event handler fixes the unit test, but I don't know why
    };

    try
    {
        domain.DoCallBack ( new AppDomainDelegateWrapper ( domain, func ).Invoke );

        return (T)domain.GetData ( "result" );
    }
    finally
    {
        AppDomain.Unload ( domain );
    }
}

public void RunInAppDomain( Action func )
{
    RunInAppDomain ( () => { func (); return 0; } );
}

///<summary>
///Provides a serializable wrapper around a delegate.
///</summary>
[Serializable]
private class AppDomainDelegateWrapper : MarshalByRefObject
{
    private readonly AppDomain _domain;
    private readonly Delegate _delegate;

    public AppDomainDelegateWrapper( AppDomain domain, Delegate func )
    {
        _domain = domain;
        _delegate = func;
    }

    public void Invoke()
    {
        _domain.SetData ( "result", _delegate.DynamicInvoke () );
    }
}

Di test di unità

[Test]
public void RunInAppDomainCleanupCheck()
{
    const string path = @"../../Output/appdomain-hanging-file.txt";

    using( var file = File.CreateText ( path ) )
    {
        file.WriteLine( "test" );
    }

    //verify that file handles that aren't closed in an AppDomain-wrapped call are cleaned up after the call returns
    Portal.ProcessService.RunInAppDomain ( () =>
    {
        //open a test file, but don't release it.  The handle should be released when the AppDomain is unloaded
        new FileStream ( path, FileMode.Open, FileAccess.ReadWrite, FileShare.None );
    } );

    //sleeping for a while doesn't make a difference
    //Thread.Sleep ( 10000 );

    //creating a new FileStream will fail if the DomainUnload event is not bound
    using( var file = new FileStream ( path, FileMode.Open, FileAccess.ReadWrite, FileShare.None ) )
    {
    }
}
InformationsquelleAutor MikeWyatt | 2009-10-02



3 Replies
  1. 75

    Applicazione domini e domini di interazione è molto sottile, quindi si deve fare in modo di capire veramente come una cosa di lavoro, prima di fare qualcosa… Mmm… diciamo, “non standard” 🙂

    Prima di tutto, il stream-creazione del metodo esegue effettivamente sul “default” di dominio (sorpresa, sorpresa!). Perché? Semplice: il metodo che si passa in AppDomain.DoCallBack è definito in un AppDomainDelegateWrapper oggetto, e l’oggetto esiste sul vostro dominio predefinito, in modo che è dove il suo metodo viene eseguito. MSDN non dire su questa “caratteristica”, ma è abbastanza facile da controllare: basta impostare un punto di interruzione in AppDomainDelegateWrapper.Invoke.

    Quindi, fondamentalmente, non senza un “wrapper” oggetto. Utilizzare il metodo statico per DoCallBack argomento.

    Ma come si fa a passare il “func” argomento in un altro dominio, in modo che il tuo metodo statico può prenderlo e eseguire?

    Il più evidente è quello di utilizzare AppDomain.SetData, o si può rotolare il vostro proprio, ma a prescindere di come esattamente si fa, c’è un altro problema: se “func” è un metodo non statico, quindi l’oggetto è definito deve essere passato in qualche modo in un altro dominio di applicazione. Potrebbe essere passati per valore (considerando che viene copiato, campo per campo) o per riferimento (creazione di un cross-dominio oggetto di riferimento in tutta la loro bellezza Remoting). Per fare prima, la classe deve essere contrassegnato con un [Serializable] attributo. Per fare questo, deve ereditare da MarshalByRefObject. Se la classe non è né, verrà generata un’eccezione al tentativo di passare l’oggetto per altro dominio. Tenete a mente, però, che il passaggio per riferimento praticamente uccide l’idea, perché il metodo sarà ancora chiamata sullo stesso dominio che l’oggetto esiste, ovvero quella di default.

    Di concludere il paragrafo di cui sopra, si sono lasciati con due opzioni: o passa un metodo definito in una classe contrassegnati con un [Serializable] attributo (e tenere a mente che l’oggetto verrà copiato), o passare da un metodo statico. Ho il sospetto che, per le proprie finalità, è necessario l’ex.

    E solo nel caso in cui non è sfuggita la vostra attenzione, vorrei far notare che la tua seconda sovraccarico di RunInAppDomain (quello che si prende Action) passa un metodo definito in una classe che non è contrassegnato [Serializable]. Non si vede alcun classe c’è? Non è necessario: con delegati anonimi contenenti variabili associate, il compilatore di creare uno per voi. E così succede che il compilatore non da fastidio a marchio generata automaticamente classe [Serializable]. Peccato, ma questa è la vita 🙂

    Detto che (un sacco di parole, non è vero? :-), e supponendo che il tuo voto non trasmettere non statico e non[Serializable] metodi, ecco il nuovo RunInAppDomain metodi:

        ///<summary>
        ///Executes a method in a separate AppDomain.  This should serve as a simple replacement
        ///of running code in a separate process via a console app.
        ///</summary>
        public static T RunInAppDomain<T>(Func<T> func)
        {
            AppDomain domain = AppDomain.CreateDomain("Delegate Executor " + func.GetHashCode(), null,
                new AppDomainSetup { ApplicationBase = Environment.CurrentDirectory });
    
            try
            {
                domain.SetData("toInvoke", func);
                domain.DoCallBack(() => 
                { 
                    var f = AppDomain.CurrentDomain.GetData("toInvoke") as Func<T>;
                    AppDomain.CurrentDomain.SetData("result", f());
                });
    
                return (T)domain.GetData("result");
            }
            finally
            {
                AppDomain.Unload(domain);
            }
        }
    
        [Serializable]
        private class ActionDelegateWrapper
        {
            public Action Func;
            public int Invoke()
            {
                Func();
                return 0;
            }
        }
    
        public static void RunInAppDomain(Action func)
        {
            RunInAppDomain<int>( new ActionDelegateWrapper { Func = func }.Invoke );
        }

    Se siete ancora con me, apprezzo 🙂

    Ora, dopo aver trascorso così tanto tempo per la fissazione di tale meccanismo, ho intenzione di dirvi che è stato inutile comunque.

    La cosa è, Appdomain non vi sarà d’aiuto per i vostri scopi. Solo prendersi cura di oggetti gestiti, mentre il codice non gestito può fuoriuscire e di atterraggio, che vuole. Il codice non gestito non so nemmeno ci sono cose come appdomain. Sa solo sui processi.

    Così, alla fine, la soluzione migliore rimane la soluzione attuale: a deporre le uova in un altro processo e di essere felice. E, sono d’accordo con le risposte precedenti, non è necessario scrivere un’altra console app per ogni caso. Passa solo un nome completo di un metodo statico, e hanno la console app caricare l’assembly, carica il tuo tipo, e richiamare il metodo. Si può effettivamente pacchetto abbastanza ordinatamente in un modo molto simile a come si è cercato con Appdomain. È possibile creare un metodo che si chiama qualcosa come “RunInAnotherProcess”, che esaminerà l’argomento, ottenere il nome completo del tipo e il nome di metodo al di fuori di esso (mentre assicurandosi che il metodo è statico) e spawn dell’applicazione console, che farà il resto.

    • Ho trovato questa tecnica è utile in fase di test del comportamento che si estende su diversi processi. Per esempio, se IIS ricicla il suo pool di applicazioni e si desidera verificare il comportamento del componente prima e dopo il riciclo. Complimenti.
    • completa il codice sorgente esempio su di esso? before and after the recycle
    • Ho questo codice sorgente: var dataSources = new List<Tuple<string, Func<IEnumerable>>> { Tuple.Create<string, Func<IEnumerable>>("TablaEvolucionVentasPolizas", () => { return listaPolizas; }), Tuple.Create<string, Func<IEnumerable>>("TablaEvolucionVentasPrimas", () => { return listaPrimas; }), Tuple.Create<string, Func<IEnumerable>>("TablaRamosVentas", () => { return listaRamos; }), };
    • stackoverflow.com/a/14320090/206730 per eseguire un delegato su un altro dominio di applicazione, è possibile utilizzare [System.AppDomain.DoCallBack()][1]. Collegati pagina di MSDN, è un ottimo esempio. Si noti che è possibile utilizzare solo i delegati di tipo [CrossAppDomainDelegate][2]. [1]: msdn.microsoft.com/en-us/library/… [2]: msdn.microsoft.com/en-us/library/…
  2. 7

    Non è necessario creare molte applicazioni console, è possibile creare una singola applicazione che riceve come parametro l’intero nome di tipo qualificato. L’applicazione del carico di che tipo e di esecuzione.

    Separare ogni cosa in piccolo processi è il metodo migliore per davvero disporre di tutte le risorse. Un dominio di applicazione non può fare a pieno le risorse di smaltimento, ma un processo.

    • Quali sono le risorse che non recuperato dal dominio di applicazione di scarico?
    • risorse autoctone.
  3. 2

    Avete considerato l’apertura di un tubo tra il ricorso principale e di quello secondario applicazioni? In questo modo si potrebbe superare più di informazioni strutturate tra le due applicazioni, senza l’analisi di standard output.

Lascia un commento