C# 正在替换进程。从AppDomains开始

C# 正在替换进程。从AppDomains开始,c#,appdomain,.net-3.5,process.start,C#,Appdomain,.net 3.5,Process.start,背景 我有一个Windows服务,它使用各种第三方DLL来处理PDF文件。这些操作可能会占用相当多的系统资源,并且在发生错误时,偶尔会出现内存泄漏。DLL是围绕其他非托管DLL的托管包装器 当前解决方案 在一个案例中,我已经通过将对一个DLL的调用包装到一个专用控制台应用程序中,并通过Process.Start()调用该应用程序来缓解这个问题。如果操作失败,并且存在内存泄漏或未释放的文件句柄,这并不重要。进程将结束,操作系统将恢复句柄 我想将同样的逻辑应用到我应用程序中使用这些DLL的其他地方。

背景

我有一个Windows服务,它使用各种第三方DLL来处理PDF文件。这些操作可能会占用相当多的系统资源,并且在发生错误时,偶尔会出现内存泄漏。DLL是围绕其他非托管DLL的托管包装器

当前解决方案

在一个案例中,我已经通过将对一个DLL的调用包装到一个专用控制台应用程序中,并通过Process.Start()调用该应用程序来缓解这个问题。如果操作失败,并且存在内存泄漏或未释放的文件句柄,这并不重要。进程将结束,操作系统将恢复句柄

我想将同样的逻辑应用到我应用程序中使用这些DLL的其他地方。然而,我对向我的解决方案中添加更多控制台项目,编写更多调用Process.Start()并解析控制台应用程序输出的锅炉板代码并不感到非常兴奋

新解决方案

专用控制台应用程序和Process.Start()的优雅替代方案似乎是使用AppDomains,如下所示:

我已经在我的应用程序中实现了类似的代码,但是单元测试没有什么前途。我在一个单独的AppDomain中为测试文件创建一个FileStream,但不处理它。然后我尝试在主域中创建另一个FileStream,但由于未释放的文件锁,它失败了

有趣的是,将一个空的DomainUnload事件添加到工作域会使单元测试通过。无论如何,我担心创建“工作者”应用程序域可能无法解决我的问题

想法

代码

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

您不必创建许多控制台应用程序,您可以创建一个将接收完整限定类型名称作为参数的应用程序。应用程序将加载该类型并执行它。

将所有内容分割成小流程是真正处理所有资源的最佳方法。进程不能处理全部资源,但进程可以

您是否考虑过主应用程序和子应用程序之间的差异?通过这种方式,您可以在两个应用程序之间传递更多结构化信息,而无需解析标准输出。

应用程序域和跨域交互是一件非常简单的事情,因此在做任何事情之前,应该确保他真正了解事情的工作原理。。。嗯。。。比如说,“非标准”:-)

首先,您的流创建方法实际上在您的“默认”域上执行(令人惊讶!)。为什么?简单:传递到
AppDomain.docCallback
的方法是在
AppDomainDelegateWrapper
对象上定义的,并且该对象存在于默认域中,因此在默认域中执行其方法。MSDN没有提到这个小“特性”,但它很容易检查:只需在
AppDomainDelegateWrapper.Invoke
中设置一个断点即可

因此,基本上,您必须在没有“包装器”对象的情况下勉强度日。对DoCallBack的参数使用静态方法

但是如何将“func”参数传递到另一个域中,以便静态方法能够拾取并执行它呢

最明显的方法是使用
AppDomain.SetData
,或者您可以自己滚动,但不管您如何精确地执行,还有另一个问题:如果“func”是一个非静态方法,那么定义它的对象必须以某种方式传递到另一个AppDomain。它可以通过值传递(而它是被复制的,一个字段一个字段地传递),也可以通过引用传递(创建一个具有远程处理所有优点的跨域对象引用)。要执行前者,必须使用
[Serializable]
属性标记该类。要执行后者,它必须从
MarshalByRefObject
继承。如果类两者都不是,则在尝试将对象传递到其他域时将引发异常。但是请记住,通过引用传递几乎扼杀了整个想法,因为您的方法仍将在对象所在的同一个域上被调用,即默认域

在结束上述段落时,您有两个选择:传递在标有
[Serializable]
属性的类上定义的方法(请记住,对象将被复制),或者传递静态方法。我想,为了你的目的,你需要前者

为了避免引起您的注意,我想指出,您的第二个
RunInAppDomain
(执行
操作的
)重载传递了在未标记为
[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 );
    }
//
///在单独的AppDomain中执行方法。这应该作为一个简单的替代品
///通过控制台应用程序在单独的进程中运行代码。
/// 
公共静态T RunInAppDomain(Func Func)
{
AppDomain域=AppDomain.CreateDomain(“委托执行器”+func.GetHashCode(),null,
新的AppDomainSetup{ApplicationBase=Environment.CurrentDirectory});
尝试
{
domain.SetData(“toInvoke”,func);
domain.DoCallBack(()=>
{ 
var f=AppDomain.CurrentDomain.GetData(“toInvoke”)作为函数;
AppDomain.CurrentDomain.SetData(“结果”,f());
});
返回(T)domain.GetData(“结果”);
}
最后
{
卸载(域);
    /// <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 );
    }