Remplacement De Processus.Démarrer avec les domaines d'application
Fond
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 ) )
{
}
}
Vous devez vous connecter pour publier un commentaire.
Domaines d'Application et les interactions entre les domaines est un très mince affaire, donc on doit s'assurer qu'il comprend vraiment comment les choses fonctionnent avant de faire quoi que ce soit... heu... disons, "non-standard" 🙂
Tout d'abord, le flux de création de la méthode exécute sur "par défaut" du domaine (surprise, surprise!). Pourquoi? Simple: la méthode que vous passez dans
AppDomain.DoCallBack
est définie sur uneAppDomainDelegateWrapper
objet, et cet objet existe sur votre domaine par défaut, c'est donc là que sa méthode est exécutée. MSDN ne pas dire à propos de cette petite "feature", mais il est assez facile à vérifier: il suffit de mettre un point d'arrêt dansAppDomainDelegateWrapper.Invoke
.Donc, fondamentalement, vous avez à faire sans un "wrapper" de l'objet. Utiliser la méthode statique pour DoCallBack de l'argument.
Mais comment voulez-vous passer vos "func" argument dans l'autre domaine, de sorte que votre méthode statique peut le ramasser et de les exécuter?
La manière la plus évidente consiste à utiliser
AppDomain.SetData
, ou vous pouvez rouler votre propre, mais quel que soit exactement comment vous le faites, il y a un autre problème: si "func" est un non-statique méthode, l'objet qu'il doit être en quelque sorte passé dans l'autre domaine d'application. Il peut être passé par valeur (alors que c'est copié, champ par champ) ou par référence (la création d'un inter-domaine objet de référence avec toute la beauté de l'accès distant). Pour les anciens, la classe doit être marqué avec un[Serializable]
attribut. Pour faire ci, il doit hériter deMarshalByRefObject
. Si la classe n'est ni de, une exception sera levée lors de la tentative de faire passer l'objet à l'autre domaine. Gardez à l'esprit, cependant, que le passage par référence assez bien tue l'idée, parce que votre méthode s'appelle toujours sur le même domaine que l'objet existe - ce qui est, celui par défaut.La conclusion du paragraphe ci-dessus, il vous reste deux options: soit de passer à une méthode définie dans une classe marquée avec un
[Serializable]
attribut (et de garder à l'esprit que l'objet sera copié), ou de passer d'une méthode statique. Je soupçonne que, pour votre application, vous aurez besoin de l'ancien.Et juste au cas où il a échappé à votre attention, je tiens à souligner que votre deuxième surcharge de
RunInAppDomain
(celui qui prendAction
) passe une méthode définie dans une classe qui n'est pas marquée[Serializable]
. Ne vois pas la classe? Vous n'avez pas à: avec les délégués anonymes contenant des variables liées, le compilateur va créer un pour vous. Et il se trouve que le compilateur n'a pas pris la peine de marque générée automatiquement classe[Serializable]
. Malheureux, mais c'est la vie 🙂Après avoir dit tout cela (beaucoup de mots, n'est-ce pas? :-), et en supposant que votre vœu de ne pas passer n'importe quel non-statique et non-
[Serializable]
méthodes, voici votre nouveauRunInAppDomain
méthodes:Si vous êtes toujours avec moi, j'apprécie 🙂
Maintenant, après tant de temps passé sur la fixation de ce mécanisme, je vais vous dire que c'est inutile de toute façon.
La chose est, les domaines d'application ne sera pas vous aider pour vos besoins. Ils ne prennent soins d'objets gérés, tandis que le code non managé peut fuir et crash tout ce qu'il veut. Code non managé ne sait même pas il ya des choses telles que les domaines d'application. Il ne sait que sur les processus.
Donc, en fin de compte, votre meilleure option reste de votre solution actuelle: il suffit de générer un autre processus et être heureux à ce sujet. Et, je suis d'accord avec les réponses précédentes, vous n'avez pas à écrire une autre console application pour chaque cas. Il suffit de passer le nom complet d'une méthode statique, et que la console de chargement des applications de votre assemblée, charge de votre type, et d'invoquer la méthode. Vous pouvez réellement paquet assez soigneusement dans un très peu de la même manière que vous avez essayé avec des domaines d'application. Vous pouvez créer une méthode appelé quelque chose comme "RunInAnotherProcess", qui traitera de l'argument le nom complet du type et de la méthode nom de celui-ci (tout en s'assurant que la méthode est statique) et donnera lieu à l'application de console, qui fera le reste.
before and after the recycle
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; }), };
[System.AppDomain.DoCallBack()][1]
. Liés page MSDN est un excellent exemple. Notez que Vous pouvez uniquement utiliser les délégués de type[CrossAppDomainDelegate][2]
. [1]: msdn.microsoft.com/en-us/library/... [2]: msdn.microsoft.com/en-us/library/...Vous n'avez pas à créer de nombreuses applications de console, vous pouvez créer une seule application qui reçoit comme paramètre le complet nom de type. L'application se charge de ce type et de l'exécuter.
Séparer le tout en minuscule processus est la meilleure méthode pour vraiment disposer de toutes les ressources. Un domaine d'application ne peut faire plein de ressources de l'élimination, mais un processus.
Avez-vous envisagé de l'ouverture d'un tuyau entre l'application principale et les sous applications? De cette façon, vous pouvez passer plus d'informations structurées entre les deux applications sans analyse standard de sortie.