Фон
У меня есть служба Windows, которая использует различные сторонние библиотеки DLL для работы с файлами PDF. Эти операции могут использовать довольно много системных ресурсов и иногда, по-видимому, страдают утечками памяти при возникновении ошибок. DLL файлы управляются обертки вокруг других неуправляемых библиотек DLL.
Текущее решение
Я уже смягчаю эту проблему в одном случае, завершая вызов одной из DLL в специальном консольном приложении и вызываю это приложение через Process.Start(). Если операция завершается с ошибкой, и есть утечки памяти или невыпущенные файлы, это не имеет большого значения. Процесс завершится, и ОС восстановит дескрипторы.
Я бы хотел применить эту же логику к другим местам моего приложения, которые используют эти DLL. Тем не менее, я не очень рад добавить дополнительные консольные проекты в свое решение и написать еще больше кода котельной, который вызывает Process.Start() и анализирует вывод консольных приложений.
Новое решение
Элегантной альтернативой выделенным консольным приложениям и Process.Start(), по-видимому, является использование AppDomains, например: http://blogs.geekdojo.net/richard/archive/2003/12/10/428.aspx.
Я реализовал аналогичный код в своем приложении, но модульные тесты не были многообещающими. Я создаю FileStream для тестового файла в отдельном AppDomain, но не удаляю его. Затем я пытаюсь создать еще один FileStream в основном домене, и он выходит из строя из-за неизданной блокировки файла.
Интересно, что добавление пустого события DomainUnload в рабочий домен делает проход unit test. Несмотря на это, я обеспокоен тем, что, возможно, создание "рабочего" 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 () );
    }
}
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 ) )
    {
    }
}