gpt4 book ai didi

c# - 用 AppDomains 替换 Process.Start

转载 作者:IT王子 更新时间:2023-10-29 03:50:31 25 4
gpt4 key购买 nike

背景

我有一个 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 .

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

有趣的是,向工作域添加一个空的 DomainUnload 事件会使单元测试通过。无论如何,我担心创建“worker”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 ) )
{
}
}

最佳答案

应用程序域和跨域交互是一件非常简单的事情,所以在做任何事情之前,应该确保他真的了解事情是如何工作的......嗯......让我们说,“非标准”:-)

首先,您的流创建方法实际上是在您的“默认”域上执行的(出乎意料!)。为什么?简单:您传入的方法 AppDomain.DoCallBackAppDomainDelegateWrapper 上定义对象,并且该对象存在于您的默认域中,因此这是执行其方法的地方。 MSDN 没有提到这个小“功能”,但检查起来很容易:只需在 AppDomainDelegateWrapper.Invoke 中设置一个断点即可。 .

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

但是您如何将您的“func”参数传递到另一个域,以便您的静态方法可以选择并执行它?

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

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

以防万一它没有引起您的注意,我想指出您的第二次过载 RunInAppDomain (采用 Action 的方法)传递定义在未标记为 [Serializable] 的类上的方法.没有看到那里有任何类(class)吗?您不必:使用包含绑定(bind)变量的匿名委托(delegate),编译器将为您创建一个。碰巧的是,编译器不会费心标记自动生成的类 [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”的方法,该方法将检查参数,从中获取完整的类型名称和方法名称(同时确保该方法是静态的)并生成控制台应用程序,它将完成剩下的工作。

关于c# - 用 AppDomains 替换 Process.Start,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1510466/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com