문제

배경

PDF 파일에서 작업을 수행하기 위해 다양한 타사 DLL을 사용하는 Windows 서비스가 있습니다. 이러한 작업은 상당히 많은 시스템 리소스를 사용할 수 있으며, 오류가 발생할 때 때때로 메모리 누출로 어려움을 겪는 것 같습니다. DLL은 다른 관리되지 않은 DLL 주변의 래퍼입니다.

현재 솔루션

전용 콘솔 앱에서 DLL 중 하나에 호출을 마무리하고 Process.Start ()을 통해 해당 앱을 호출하여 이미이 문제를 완화하고 있습니다. 작업이 실패하고 메모리 누출 또는 발표되지 않은 파일 핸들이 있으면 실제로 중요하지 않습니다. 프로세스가 끝나고 OS는 핸들을 복구합니다.

이 DLL을 사용하는 내 앱의 다른 장소에 동일한 논리를 적용하고 싶습니다. 그러나 솔루션에 더 많은 콘솔 프로젝트를 추가하고 Process.Start () 및 콘솔 앱의 출력을 구문 분석하는 더 많은 보일러 플레이트 코드를 작성하는 것에 대해별로 흥분하지 않습니다.

새로운 솔루션

전용 콘솔 앱 및 프로세스에 대한 우아한 대안은 다음과 같은 앱 도메인을 사용하는 것 같습니다. http://blogs.geekdojo.net/richard/archive/2003/12/10/428.aspx.

응용 프로그램에서 유사한 코드를 구현했지만 장치 테스트는 유망하지 않았습니다. 별도의 appDomain에서 테스트 파일에 대한 파일 스트림을 만듭니다. 그런 다음 기본 도메인에서 다른 파일 스트림을 만들려고 시도하며 미공개 파일 잠금으로 인해 실패합니다.

흥미롭게도, 빈 도메인로드 이벤트를 작업자 도메인에 추가하면 단위 테스트가 통과됩니다. 그럼에도 불구하고, 나는 "작업자"앱 도메인을 만드는 것이 내 문제를 해결하지 못할 것이라고 걱정하고 있습니다.

생각?

코드

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

단위 테스트

[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 ) )
    {
    }
}
도움이 되었습니까?

해결책

애플리케이션 도메인과 크로스 도메인 상호 작용은 매우 얇은 문제이므로, 그가 무엇이든하기 전에 일이 어떻게 작동하는지 이해해야합니다 ... 음 ... "비표준":-)

우선, 스트림 제작 방법은 실제로 "기본"도메인 (Surprise-Surprise!)에서 실행됩니다. 왜요? 단순 : 전달하는 방법 AppDomain.DoCallBack an에 정의됩니다 AppDomainDelegateWrapper 객체 및 해당 객체는 기본 도메인에 존재하므로 메소드가 실행되는 곳입니다. MSDN 은이 작은 "기능"에 대해 말하지 않지만 확인하기에 쉽습니다. AppDomainDelegateWrapper.Invoke.

따라서 기본적으로 "래퍼"객체없이해야합니다. Docallback의 인수에 정적 메소드를 사용하십시오.

그러나 정적 메소드를 선택하고 실행할 수 있도록 "func"인수를 다른 도메인으로 어떻게 전달합니까?

가장 분명한 방법은 사용하는 것입니다 AppDomain.SetData, 또는 당신이 직접 굴릴 수는 있지만, 정확히 어떻게하는지에 관계없이 또 다른 문제가 있습니다. "func"가 비 정적 메소드 인 경우 정의 된 객체는 어떻게 든 다른 appdomain으로 전달되어야합니다. 값 (필드별로 복사, 필드) 또는 참조 (리모 팅의 모든 아름다움과 함께 크로스 도메인 객체 참조 생성)에 의해 전달 될 수 있습니다. 이전을하기 위해서는 수업에 표시되어야합니다. [Serializable] 기인하다. 후자를하기 위해서는 상속해야합니다 MarshalByRefObject. 클래스가 없으면 물체를 다른 도메인으로 전달하려는 시도에 예외가 발생합니다. 그러나 참조로 통과하는 것은 객체가 존재하는 것과 동일한 도메인, 즉 기본값으로 여전히 호출되기 때문에 참조로 통과하면 전체 아이디어를 거의 죽입니다.

위의 단락을 마무리하면 두 가지 옵션이 남아 있습니다. [Serializable] 속성 (그리고 객체가 복사 될 것임을 명심하거나 정적 메소드를 통과하십시오. 나는 당신의 목적을 위해 당신은 전자가 필요하다고 생각합니다.

그리고 그것이 당신의 관심을 피한 경우를 대비하여, 나는 당신의 두 번째 과부하가 RunInAppDomain (취하는 사람 Action) 표시되지 않은 클래스에 정의 된 메소드를 전달합니다. [Serializable]. 거기에 수업이 보이지 않습니까? 당신은 할 필요가 없습니다 : 바운드 변수를 포함하는 익명 대표단을 사용하면 컴파일러가 당신을 위해 하나를 만들 것입니다. 그리고 컴파일러가자가 생성 클래스를 표시하는 것을 귀찮게하지 않습니다. [Serializable]. 불행하지만 이것은 삶입니다 :-)

그 모든 것을 말하면 (많은 단어가 아닌가? :-), 당신의 서원이 비 정적 및 비 전달하지 말라고 가정합니다.[Serializable] 방법, 여기에 새로운 것이 있습니다 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 );
    }

당신이 여전히 나와 함께 있다면 감사합니다 :-)

이제 그 메커니즘을 고치는 데 많은 시간을 보낸 후에, 나는 어쨌든 의도가 없다고 말할 것입니다.

문제는 AppDomains가 귀하의 목적에 도움이되지 않는다는 것입니다. 관리되는 객체 만 관리하지만 관리되지 않는 코드는 원하는 모든 것을 누출하고 충돌시킬 수 있습니다. 관리되지 않는 코드는 AppDomains와 같은 것이 있다는 것을 알지 못합니다. 프로세스에 대해서만 알고 있습니다.

결국, 최선의 선택은 현재 솔루션으로 남아 있습니다. 다른 프로세스를 생산하고 행복하십시오. 그리고 이전 답변에 동의 할 것입니다. 각 케이스에 대해 다른 콘솔 앱을 작성할 필요가 없습니다. 정적 메소드의 자격을 갖춘 이름을 전달하고 콘솔 앱에 어셈블리를로드하고 유형을로드하고 메소드를 호출하도록하십시오. 당신은 실제로 appDomains에서 시도한 것과 거의 같은 방식으로 깔끔하게 포장 할 수 있습니다. "runinanotherprocess"와 같은 메소드를 만들 수 있습니다.이 방법은 인수를 검사하고 전체 유형 이름과 메소드 이름을 가져 오기 (메소드가 정적인지 확인하는 동안)를 만들고 나머지 작업을 수행 할 콘솔 앱을 생성 할 수 있습니다.

다른 팁

많은 콘솔 애플리케이션을 만들 필요가 없으며 전체 자격을 갖춘 유형 이름을 매개 변수로 수신 할 단일 응용 프로그램을 만들 수 있습니다. 응용 프로그램은 해당 유형을로드하여 실행합니다.
모든 것을 작은 프로세스로 분리하는 것은 모든 자원을 실제로 폐기하는 가장 좋은 방법입니다. an 응용 프로그램 도메인 완전한 리소스 처분을 할 수는 없지만 프로세스는 가능합니다.

당신은 고려 했습니까? 파이프 열기 기본 응용 프로그램과 하위 응용 프로그램 사이에? 이렇게하면 표준 출력을 구문 분석하지 않고 두 응용 프로그램간에 더 구조화 된 정보를 전달할 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top