سؤال

خلفية

لدي خدمة Windows تستخدم العديد من DLL من الطرف الثالث لأداء العمل على ملفات PDF. يمكن أن تستخدم هذه العمليات قليلا من موارد النظام، ويبدو أن أحيانا تعاني من تسرب الذاكرة عند حدوث أخطاء. يتم إدارة DLL مغلفة حول DLLs الأخرى غير المدارة.

الحل الحالي

أنا مخفيف بالفعل في هذه المشكلة في حالة واحدة عن طريق إلزام مكالمة إلى أحد DLLs في تطبيق وحدة تحكم مخصصة واستدعاء هذا التطبيق عبر Process.Start (). إذا فشلت العملية وهناك تسرب الذاكرة أو مقابض ملفات غير مرئية، فهذا لا يهم حقا. ستنتهي العملية وسوف يتعافى نظام التشغيل المقابض.

أود تطبيق هذا المنطق نفسه في الأماكن الأخرى في تطبيقي الذي يستخدم هذه DLLs. ومع ذلك، أنا لست متحمسا بشكل رهيب لإضافة المزيد من مشاريع وحدة التحكم إلى حولي، والكتابة حتى رمز لوحة الغلاية التي تستدعي process.start () ويوزع إخراج تطبيقات وحدة التحكم.

حل جديد

يبدو أن هناك بديلا أنيقا لتطبيقات وحدة التحكم المخصصة والمعالجات. يبدو أن استخدام AppDomains، مثل هذا: http://blogs.geekdojo.net/richard/archive/2003/12/10/428.aspx..

لقد قمت بتطبيق رمز مماثل في طلبي، لكن اختبارات الوحدات لم تكن واعدة. أقوم بإنشاء ملف FILESTREAM إلى ملف اختبار في تطبيق AppDomain منفصل، لكن لا تتخلصه. ثم أحاول إنشاء مقاميرا أخرى في المجال الرئيسي، وفشلها بسبب قفل الملفات غير المصنعة.

ومن المثير للاهتمام، إضافة حدث Domainunload فارغ إلى مجال العامل يجعل تمرير اختبار الوحدة. بغض النظر، أشعر بالقلق من أنه ربما لن يحل Appdomains "عامل" "عامل" مشكلتي.

أفكار؟

الرمز

/// <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 ) )
    {
    }
}
هل كانت مفيدة؟

المحلول

مجالات التطبيق والتفاعل عبر المجال هو مسألة رقيقة جدا، لذلك يجب على المرء أن يتأكد من أنه يفهم حقا كيف يعمل الشيء قبل أن تفعل أي شيء ... MMM ... دعنا نقول، "غير قياسي" :-)

بادئ ذي بدء، تنفذ طريقة إنشاء التدفق الخاصة بك في المجال "الافتراضي" (مفاجأة مفاجأة!). لماذا ا؟ بسيط: الطريقة التي تمر بها AppDomain.DoCallBack يتم تعريفه على 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. يعرف فقط عن العمليات.

لذلك، في النهاية، يبقى خيارك الأفضل الحل الحالي: فقط تفرخ عملية أخرى وتكون سعيدا به. وأود أن أتفق مع الإجابات السابقة، لا يتعين عليك كتابة تطبيق وحدة التحكم الأخرى لكل حالة. قم فقط بمرور اسم مؤهل بالكامل لطريقة ثابتة، ولديه تطبيق Console تحميل التجميع الخاص بك، قم بتحميل نوعك، واستدعاء الطريقة. يمكنك فعلا حزمة أنها بدقة للغاية بنفس الطريقة التي حاولت بها مع AppDomains. يمكنك إنشاء طريقة تسمى شيئا مثل "Runinanan Trocess"، والتي ستفحص الوسيطة، احصل على اسم النوع الكامل واسم الطريقة خارجها (أثناء التأكد من أن الطريقة ثابتة) وتفرخ تطبيق وحدة التحكم، والذي سيفعل الباقي.

نصائح أخرى

ليس لديك لإنشاء العديد من تطبيقات وحدة التحكم، يمكنك إنشاء تطبيق واحد يتلقى كمعلمة اسم النوع المؤهل الكامل. سيتم تحميل التطبيق هذا النوع وتنفيذه.
فصل كل شيء في العمليات الصغيرة هو أفضل طريقة للتخلص من جميع الموارد حقا. أ مجال التطبيق لا يمكن القيام بالتخلص من الموارد الكاملة، ولكن عملية يمكن.

هل نظرت فتح أنبوب بين التطبيق الرئيسي والتطبيقات الفرعية؟ بهذه الطريقة يمكنك تمرير معلومات أكثر تنظيما بين التطبيقين دون تحليل الإخراج القياسي.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top