Arrière-plan
J'ai un service Windows qui utilise différentes Dll de tiers pour effectuer des travaux sur les fichiers PDF. Ces opérations peuvent utiliser très peu de ressources système, et parfois semblent souffrir de fuites de mémoire lorsque des erreurs se produisent. Les Dll sont gérés wrappers autour d'autres Dll non gérées.
Solution Actuelle
Je suis déjà atténuer ce problème dans un cas, en l'enveloppant d'un appel à l'une des Dll dans une application console et l'appel que app par le biais de Processus.Start(). Si l'opération échoue et il y a des fuites de mémoire ou inédits de descripteurs de fichiers, il n'a pas vraiment d'importance. Le processus et le système d'exploitation sera de récupérer les poignées.
J'aimerais appliquer cette même logique à d'autres endroits dans mon application qui utilisent ces Dll. Cependant, je ne suis pas terriblement excité à propos de l'ajout de plus de la console de projets de ma solution, et écrit même plus de la chaudière-plaque de code qui appelle le Processus.Start() et analyse de la sortie de la console apps.
Nouvelle Solution
Une alternative élégante à la console dédiée applications et Processus.Start() semble être l'utilisation de domaines d'application, comme ceci: http://blogs.geekdojo.net/richard/archive/2003/12/10/428.aspx.
J'ai mis en place un code similaire dans mon application, mais les tests n'ont pas été prometteurs. J'ai créer un FileStream pour un fichier de test dans un autre domaine d'application, mais ne pas la jeter. J'ai ensuite essayer de créer un autre FileStream dans le domaine principal, et il échoue en raison de la inédits de verrouillage de fichier.
Fait intéressant, l'ajout d'un vide DomainUnload événement pour le travailleur de domaine fait l'unité de test. Peu importe, je suis inquiète de ce que peut-être la création d'un "travailleur", de domaines d'application ne suffit pas à résoudre mon problème.
Pensées?
Le Code
/// <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 () );
}
}
L'unité de 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 ) )
{
}
}