Domanda

Sfondo

Ho un servizio di Windows che utilizza diverse DLL di terze parti per eseguire lavori su file PDF. Queste operazioni possono utilizzare un po 'di risorse di sistema, e di tanto in tanto sembrano soffrire di perdite di memoria quando si verificano errori. Le DLL sono gestiti wrapper attorno ad altre DLL non gestite.

Soluzione corrente

Sono già mitigare questo problema in un caso avvolgendo una chiamata a una delle DLL in una console applicazione dedicata e chiamata che applicazione tramite Process.Start (). Se l'operazione non riesce e non ci sono perdite di memoria o handle di file inediti, non ha molta importanza. Il processo terminerà e il sistema operativo sarà recuperare le maniglie.

Mi piacerebbe applicare questa stessa logica agli altri luoghi in mio app che utilizzano queste DLL. Tuttavia, io non sono terribilmente eccitato per l'aggiunta di ulteriori progetti di console per la mia soluzione, e la scrittura ancora più codice della caldaia-piastra che chiama Process.Start () e analizza l'output delle applicazioni della console.

Nuova Soluzione

Un elegante alternativa alle applicazioni di console dedicate e Process.Start () sembra essere l'uso di AppDomain, in questo modo: http://blogs.geekdojo.net/richard/archive/2003/12/10/428.aspx .

Ho implementato il codice simile nella mia richiesta, ma i test di unità non sono stati promettenti. Creo un FileStream per un file di prova in un AppDomain separato, ma non gettare esso. Ho quindi tentare di creare un altro FileStream nel dominio principale, e viene a mancare a causa del blocco di file di inedito.

interessante, aggiungendo un evento vuoto DomainUnload al dominio lavoratore rende il passaggio unit test. Indipendentemente da ciò, io sono preoccupato del fatto che forse la creazione di "lavoratore" AppDomain non risolverà 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 () );
    }
}

La prova 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 ) )
    {
    }
}
È stato utile?

Soluzione

domini di applicazione e l'interazione tra domini è una questione molto sottile, così si dovrebbe fare in modo che capisce davvero come il lavoro prima di fare qualsiasi cosa ... Mmm ... Diciamo, "non standard": -)

Prima di tutto, il metodo di flusso di creazione in realtà esegue sul tuo dominio "default" (sorpresa-sorpresa!). Perché? Semplice: il metodo che si passa in AppDomain.DoCallBack è definita su un oggetto AppDomainDelegateWrapper, e che oggetto esiste sul dominio predefinito, in modo che è dove viene eseguito il suo metodo. MSDN non dice su questo piccolo "caratteristica", ma è abbastanza facile da controllare:. Basta impostare un punto di interruzione in AppDomainDelegateWrapper.Invoke

Quindi, in sostanza, si deve fare a meno di un oggetto "wrapper". Utilizzare il metodo statico per l'argomento di DoCallBack.

Ma come si fa a passare il tuo argomento "func" in altro dominio in modo che il metodo statico può raccoglierlo ed eseguire?

Il modo più evidente è quello di utilizzare AppDomain.SetData, oppure si può rotolare il proprio, ma a prescindere da come esattamente lo fai, c'è un altro problema: se "func" è un metodo non statico, l'oggetto che è definito on deve essere in qualche modo passato all'altro dominio applicazione. Esse possono essere trasmesse mediante valore (mentre viene copiato, campo per campo) o mediante riferimento (creazione di un riferimento oggetto interdominio con tutta la bellezza della Remoting). Per fare prima, la classe deve essere contrassegnato con un attributo [Serializable]. Per fare quest'ultima, deve ereditare da MarshalByRefObject. Se la classe non è né, un'eccezione sarà gettato dopo il tentativo di passare l'oggetto all'altro dominio. Tenete a mente, però, che il passaggio per riferimento uccide praticamente l'intera idea, perché il vostro metodo sarà ancora chiamato nello stesso dominio che l'oggetto esiste sul -. Vale a dire, quella di default

A conclusione del paragrafo precedente, si sono lasciati con due opzioni: o passare un metodo definito su una classe contrassegnata con un attributo [Serializable] (e tenere a mente che l'oggetto sarà copiato), o passare un metodo statico. Ho il sospetto che, per i vostri scopi, è necessario l'ex.

E solo nel caso è sfuggito alla vostra attenzione, vorrei far notare che la vostra seconda sovraccarico RunInAppDomain (quello che prende Action) passa un metodo definito in una classe che non è segnato [Serializable]. Non vedere alcuna classe ci? Non è necessario a: con i delegati anonimo contenente variabili legate, il compilatore creerà uno per te. E si dà il caso che il compilatore non si preoccupa di marcare quella classe [Serializable] generata automaticamente. Sfortunato, ma questa è la vita: -)

Detto questo (un sacco di parole, non è vero :-), e supponendo che il voto di non passare i metodi non statici e non [Serializable], qui sono i tuoi nuovi metodi RunInAppDomain:?

    /// <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, mi rendo conto: -)

Ora, dopo aver trascorso così tanto tempo, che fissa quel meccanismo, ho intenzione di dirvi che è era inutile comunque.

La cosa è, AppDomain non vi aiuterà per i vostri scopi. Si prendono cura solo di oggetti gestiti, mentre il codice non gestito può fuoriuscire e crash tutto ciò che vuole. codice non gestito non sa nemmeno ci sono cose come AppDomain. Si conosce solo sui processi.

Così, alla fine, la soluzione migliore rimane la soluzione corrente: basta deporre le uova un altro processo ed essere felice. E, Sono d'accordo con le risposte precedenti, non c'è bisogno di scrivere un altro console app per ogni caso. Basta passare un nome completo di un metodo statico, e hanno la console app caricare la vostra assemblea, caricare il tuo tipo, e richiamare il metodo. Si può effettivamente pacchetto abbastanza ordinatamente in un modo molto simile a come si è tentato con AppDomain. È possibile creare un metodo chiamato qualcosa come "RunInAnotherProcess", che esaminerà l'argomento, ottenere il nome di tipo completo e il nome del metodo da esso (mentre assicurandosi il metodo è statico) e deporre le uova la consoleapp, che farà il resto.

Altri suggerimenti

Non è necessario creare molte applicazioni console, è possibile creare una singola applicazione che riceverà come parametro il nome completo di tipo qualificato. L'applicazione caricherà quel tipo ed eseguirlo.
Separare tutto in processi piccoli è il metodo migliore per smaltire in realtà tutte le risorse. Un non può fare pieno le risorse lo smaltimento, ma un processo può.

Avete considerato l'apertura di un tubo tra l'applicazione principale e le applicazioni sub? In questo modo si potrebbe passare le informazioni più strutturato tra le due applicazioni, senza il parsing di uscita standard.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top