背景
我有一个使用各种第三方DLL文件处理PDF文件的Windows服务。这些操作可能会使用大量系统资源,并且在发生错误时偶尔似乎会出现内存泄漏。这些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事件使单元测试通过。无论如何,我担心创建“工作”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 ) )
{
}
}
var dataSources = new List<Tuple<string, Func<IEnumerable>>> { Tuple.Create<string, Func<IEnumerable>>("TablaEvolucionVentasPolizas", () => { return listaPolizas; }), Tuple.Create<string, Func<IEnumerable>>("TablaEvolucionVentasPrimas", () => { return listaPrimas; }), Tuple.Create<string, Func<IEnumerable>>("TablaRamosVentas", () => { return listaRamos; }), };
- Kiquenet[System.AppDomain.DoCallBack()][1]
。 链接的MSDN页面有一个很好的例子。请注意,您只能使用类型为[CrossAppDomainDelegate][2]
的委托。 - Kiquenet