Frage

Hintergrund

Ich habe einen Windows-Dienst, verschiedenen Drittanbieter-DLLs verwendet Arbeit an PDF-Dateien auszuführen. Diese Operationen können einiges an Systemressourcen, und gelegentlich von Speicherlecks zu leiden scheinen, wenn Fehler auftreten. Die DLLs werden verwaltete Wrapper um andere nicht verwaltete DLLs.

Aktuelle Lösung

Ich bin Milderung dieses Problem bereits in einem Fall durch einen Anruf an eine der DLLs in einer eigenen Konsole App Einwickeln und dass App über Process.Start () aufrufen. Wenn der Vorgang fehlschlägt und es gibt Speicherlecks oder nicht freigegebene Datei-Handles, ist es nicht wirklich wichtig. Der Prozess wird beendet und das Betriebssystem wird die Griffe erholen.

Ich mag diese Logik zu den anderen Orten in meiner app anzuwenden, die diese DLLs verwenden. Allerdings bin ich nicht sehr begeistert über mehr Konsolenprojekte zu meiner Lösung hinzufügen und noch mehr Kesselblech Code schreiben, Process.Start () und analysiert die Ausgabe der Konsole Anwendungen aufrufen.

Neue Lösung

Eine elegante Alternative zu dedizierten Konsolenanwendungen und Process.Start () scheint die Verwendung von AppDomains, so zu sein: http://blogs.geekdojo.net/richard/archive/2003/12/10/428.aspx .

Ich habe einen ähnlichen Code in meiner Anwendung implementiert, aber die Unit-Tests nicht vielversprechend. Ich erstelle ein Filestream in eine Testdatei in einem separaten AppDomain, aber es nicht entsorgen. Ich versuche dann eine anderen Filestream in der Hauptdomäne zu erstellen, und es scheitert aufgrund der nicht freigegebenen Datei zu sperren.

Interessanterweise Hinzufügen eines leeren DomainUnload Ereignis an die Arbeiter Domäne macht das Gerät Testdurchlauf. Egal, ich bin besorgt darüber, dass vielleicht „Arbeiter“ AppDomains Schaffung nicht mein Problem lösen.

Die Gedanken?

Der Kodex

/// <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 () );
    }
}

Der Unit-Test

[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 ) )
    {
    }
}
War es hilfreich?

Lösung

Anwendungsdomänen und domänenübergreifende Interaktion eine sehr dünne Angelegenheit ist, so sollte man darauf achten, er wirklich versteht, wie etwas Arbeit vor etwas zu tun ... Mmm ... Lassen Sie uns sagen, „non-standard“: -)

Zu allererst Stream schöpf Methode führt tatsächlich auf „default“ Domain (Überraschung-Überraschung!). Warum? Ganz einfach: die Methode, die Sie in AppDomain.DoCallBack passieren auf einem AppDomainDelegateWrapper Objekt definiert, und das Objekt existiert in der Standarddomäne, so dass ist, wo seine Methode ausgeführt wird. MSDN nicht sagen über dieses kleine „Feature“, aber es ist einfach genug, um zu überprüfen:. Nur einen Haltepunkt in AppDomainDelegateWrapper.Invoke gesetzt

Also, im Grunde, müssen Sie ohne einen „Wrapper“ Objekt behelfen. Verwenden Sie statische Methode für DoCallBack Argument.

Aber wie geben Sie Ihre „Func“ Argument in die andere Domäne, so dass Ihre statische Methode es abholen und ausführen kann?

Der offensichtlichste Weg ist AppDomain.SetData zu verwenden, oder Sie können Ihre eigene Rolle, aber unabhängig davon, wie genau Sie es tun, gibt es ein weiteres Problem: Wenn „Func“ eine nicht-statische Methode ist, dann ist das Objekt, dass es definiert muß sich irgendwie in die andere Appdomain weitergegeben werden. Es kann von Wert übergeben werden, entweder (während es wird kopiert, Feld für Feld) oder durch Referenz (eine domänenübergreifende Objektreferenz mit all der Schönheit der Schaffung Remoting). Zu tun ehemalige, hat die Klasse mit einem [Serializable] Attribute gekennzeichnet werden. Gehen Sie letztere hat es von MarshalByRefObject erben. Wenn die Klasse weder ist, wird eine Ausnahme bei Versuch geworfen werden, um das Objekt in der anderen Domäne zu übergeben. Beachten Sie aber, dass so ziemlich durch Bezugnahme vorbei tötet die ganze Idee, denn Ihre Methode wird immer noch auf der gleichen Domain aufgerufen werden, das Objekt existiert auf -. Das heißt, die standardmäßig ein

den oben Absatz Abschluss, werden Sie mit zwei Optionen übrig: entweder ein Verfahren auf einen mit einem [Serializable] Attribute gekennzeichnet Klasse definiert passieren (und denken Sie daran, dass das Objekt wird kopiert) oder eine statische Methode übergeben. Ich vermute, dass, für Ihre Zwecke, Sie werden die ehemaligen benötigen.

Und für alle Fälle hat es Ihre Aufmerksamkeit entgangen, würde Ich mag darauf hinweisen, dass Ihre zweite Überlastung von RunInAppDomain (derjenige, der Action nimmt) auf eine Klasse definiert geht eine Methode, die nicht [Serializable] markiert. siehe jede Klasse nicht da? Sie müssen nicht: mit anonym Delegierten gebundene Variablen enthalten, wird der Compiler für Sie erstellen. Und es passiert einfach so, dass der Compiler die Mühe nicht, dass automatisch generierte Klasse [Serializable] zu markieren. Bedauerlich, aber das ist das Leben: -)

Nachdem alles gesagt, dass (viele Worte, ist es nicht :-), und Ihr Gelübde vorausgesetzt keine nicht-statische und nicht-[Serializable] Methoden weitergeben, sind hier Ihre neue RunInAppDomain Methoden:

    /// <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 );
    }

Wenn Sie immer noch bei mir bist, ich schätze: -)

Jetzt, nach so viel Zeit, dass Mechanismus auf Festsetzung, ich werde Ihnen sagen, dass ohnehin zwecklos war.

Die Sache ist, AppDomains werden Sie nicht für Ihre Zwecke helfen. Sie nehmen nur Pflege von gemanagten Objekten, während nicht verwalteten Code austreten kann und Absturz alle es will. Unmanaged Code noch nicht einmal weiß, es gibt solche Dinge wie AppDomains. Es kennt nur Prozesse.

Also, am Ende der beste Option bleibt Ihre aktuelle Lösung: nur einen weiteren Prozess erzeugen und darüber freuen. Und würde ich mit den bisherigen Antworten einverstanden sind, Sie keine andere Konsole app für jeden Fall zu schreiben. Übergeben Sie einfach einen vollqualifizierten Namen einer statischen Methode, und haben die Konsole App Ihrer Assembly laden, laden Sie Ihre Art, und die Methode aufrufen. Sie können es tatsächlich verpacken ziemlich ordentlich in einem sehr viel die gleiche Weise, wie Sie mit AppDomains versucht. Sie können eine Methode namens so etwas wie „RunInAnotherProcess“ erstellen, die das Argument prüfen wird, erhalten den vollständigen Typnamen und Methodennamen aus ihm heraus (wobei er darauf achtete die Methode statisch ist) und laichen die KonsoleApp, die den Rest tun.

Andere Tipps

Sie müssen nicht viele Konsolenanwendungen erstellen, können Sie eine einzelne Anwendung erstellen, die als Parameter den vollständigen Typnamen erhalten. Die Anwendung wird diese Art laden und ausführen.
Die Trennung alles in winzige Prozesse ist die beste Methode, um wirklich alle Ressourcen zu verfügen. Eine Anwendungsdomäne können sämtliche Ressourcen nicht entsorgen, sondern ein Prozess kann.

Haben Sie darüber nachgedacht, Öffnen eines Rohr zwischen die Hauptanwendung und die Unter Anwendungen? Auf diese Weise könnten Sie besser strukturierte Informationen zwischen den beiden Anwendungen übergeben, ohne die Standardausgabe Parsen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top