Comme les suggestions précédentes sont de nature assez générique, j'ai pensé qu'il pourrait être utile de poster ma propre bataille contre cette exception avec des exemples de code spécifiques, les changements d'arrière-plan que j'ai mis en œuvre pour que cette exception se produise, et comment je l'ai résolue.
D'abord, la version TL;DR : J'utilisais une dll interne écrite en C++ (non gérée). J'ai transmis un tableau d'une taille spécifique à partir de mon exécutable .NET. Le code non géré a tenté d'écrire dans un emplacement de tableau qui n'était pas alloué par le code géré. Cela a provoqué une corruption de la mémoire qui a ensuite été configurée pour être collectée par le garbage collector. Lorsque le ramasseur d'ordures se prépare à collecter la mémoire, il vérifie d'abord l'état de la mémoire (et des limites). Quand il trouve la corruption, BOOM .
Maintenant, la version détaillée :
J'utilise une dll non gérée développée en interne, écrite en C++. Mon propre développement d'interface graphique est en C# .Net 4.0. J'appelle une variété de ces méthodes non gérées. Cette dll agit effectivement comme ma source de données. Un exemple de définition externe de la dll :
[DllImport(@"C:\Program Files\MyCompany\dataSource.dll",
EntryPoint = "get_sel_list",
CallingConvention = CallingConvention.Winapi)]
private static extern int ExternGetSelectionList(
uint parameterNumber,
uint[] list,
uint[] limits,
ref int size);
J'intègre ensuite les méthodes dans ma propre interface pour les utiliser dans l'ensemble de mon projet :
/// <summary>
/// Get the data for a ComboBox (Drop down selection).
/// </summary>
/// <param name="parameterNumber"> The parameter number</param>
/// <param name="messageList"> Message number </param>
/// <param name="valueLimits"> The limits </param>
/// <param name="size"> The maximum size of the memory buffer to
/// allocate for the data </param>
/// <returns> 0 - If successful, something else otherwise. </returns>
public int GetSelectionList(uint parameterNumber,
ref uint[] messageList,
ref uint[] valueLimits,
int size)
{
int returnValue = -1;
returnValue = ExternGetSelectionList(parameterNumber,
messageList,
valueLimits,
ref size);
return returnValue;
}
Un exemple d'appel de cette méthode :
uint[] messageList = new uint[3];
uint[] valueLimits = new uint[3];
int dataReferenceParameter = 1;
// BUFFERSIZE = 255.
MainNavigationWindow.MainNavigationProperty.DataSourceWrapper.GetSelectionList(
dataReferenceParameter,
ref messageList,
ref valueLimits,
BUFFERSIZE);
Dans l'interface graphique, on navigue à travers différentes pages contenant une variété de graphiques et d'entrées utilisateur. La méthode précédente m'a permis d'obtenir les données pour remplir ComboBoxes
. Un exemple de ma configuration de navigation et de mon appel à l'époque avant cette exception :
Dans ma fenêtre d'hôte, je configure une propriété :
/// <summary>
/// Gets or sets the User interface page
/// </summary>
internal UserInterfacePage UserInterfacePageProperty
{
get
{
if (this.userInterfacePage == null)
{
this.userInterfacePage = new UserInterfacePage();
}
return this.userInterfacePage;
}
set { this.userInterfacePage = value; }
}
Puis, au besoin, je navigue vers la page :
MainNavigationWindow.MainNavigationProperty.Navigate(
MainNavigation.MainNavigationProperty.UserInterfacePageProperty);
Tout a fonctionné assez bien, même si j'ai eu de sérieux problèmes de reptation. Lorsque l'on navigue en utilisant l'objet ( Méthode NavigationService.Navigate (Objet) ), le paramètre par défaut de l IsKeepAlive
la propriété est true
. Mais le problème est plus grave que cela. Même si vous définissez le IsKeepAlive
dans le constructeur de cette page spécifiquement pour false
il est toujours laissé seul par le ramasseur d'ordures comme s'il était true
. Pour beaucoup de mes pages, ce n'était pas un problème. Elles avaient de petites empreintes mémorielles et pas grand-chose à faire. Mais beaucoup d'autres de ces pages comportaient de grands graphiques très détaillés à des fins d'illustration. Il n'a pas fallu longtemps pour que l'utilisation normale de cette interface par les opérateurs de notre équipement provoque d'énormes allocations de mémoire qui ne s'effacent jamais et finissent par bloquer tous les processus de la machine. Après que le rush du développement initial se soit calmé, passant d'un tsunami à un raz-de-marée, j'ai finalement décidé de m'attaquer aux fuites de mémoire une fois pour toutes. Je ne vais pas entrer dans les détails de toutes les astuces que j'ai mises en œuvre pour nettoyer la mémoire ( Référence faible aux images, en désactivant les gestionnaires d'événements lors de Unload(), en utilisant une minuterie personnalisée mettant en œuvre la fonction IWeakEventListener interface, etc...). Le changement clé que j'ai fait était de naviguer vers les pages en utilisant l'Uri au lieu de l'objet ( Méthode NavigationService.Navigate (Uri) ). Il existe deux différences importantes dans l'utilisation de ce type de navigation :
-
IsKeepAlive
est réglé sur false
par défaut.
- Le ramasseur d'ordures va maintenant essayer de nettoyer l'objet de navigation comme si
IsKeepAlive
a été fixé à false
.
Maintenant, ma navigation ressemble à ça :
MainNavigation.MainNavigationProperty.Navigate(
new Uri("/Pages/UserInterfacePage.xaml", UriKind.Relative));
Autre chose à noter ici : Cela n'affecte pas seulement la manière dont les objets sont nettoyés par le ramasseur d'ordures, mais aussi la manière dont ils sont initialement alloué en mémoire comme j'allais bientôt le découvrir.
Tout semble avoir bien fonctionné. Ma mémoire se nettoyait rapidement pour revenir à un état proche de l'état initial lorsque je naviguais dans les pages à forte intensité graphique, jusqu'à ce que j'atteigne cette page particulière avec cet appel particulier à la dll dataSource pour remplir quelques comboBoxes. Ensuite, j'ai eu ce désagrément FatalEngineExecutionError
. Après des jours de recherche et de découverte de vagues suggestions, ou de solutions très spécifiques qui ne s'appliquaient pas à moi, et après avoir utilisé toutes les armes de débogage de mon arsenal personnel de programmation, j'ai finalement décidé que la seule façon de résoudre ce problème était de reconstruire une copie exacte de cette page particulière, élément par élément, méthode par méthode, ligne par ligne, jusqu'à ce que je trouve enfin le code qui génère cette exception. C'était aussi fastidieux et pénible que je le laisse entendre, mais j'ai fini par le retrouver.
Il s'est avéré que le problème venait de la façon dont la dll non gérée allouait de la mémoire pour écrire des données dans les tableaux que j'envoyais pour les remplir. Cette méthode particulière regardait en fait le numéro du paramètre et, à partir de cette information, allouait un tableau d'une taille particulière en fonction de la quantité de données qu'elle s'attendait à écrire dans le tableau que j'envoyais. Le code qui s'est planté :
uint[] messageList = new uint[2];
uint[] valueLimits = new uint[2];
int dataReferenceParameter = 1;
// BUFFERSIZE = 255.
MainNavigationWindow.MainNavigationProperty.DataSourceWrapper.GetSelectionList(
dataReferenceParameter,
ref messageList,
ref valueLimits,
BUFFERSIZE);
Ce code peut sembler identique à l'exemple ci-dessus, mais il comporte une petite différence. La taille du tableau que j'alloue est 2 no 3 . Je l'ai fait parce que je savais que cette ComboBox particulière n'aurait que deux éléments de sélection, contrairement aux autres ComboBox de la page qui avaient toutes trois éléments de sélection. Cependant, le code non géré n'a pas vu les choses comme je les voyais. Il a pris le tableau que je lui ai remis et a essayé d'écrire une taille 3 dans ma taille 2 allocation, et c'était tout. * bang ! * * crash ! * J'ai changé la taille de l'allocation à 3, et l'erreur a disparu.
Ce code particulier avait déjà fonctionné sans cette erreur pendant au moins un an. Mais le simple fait de naviguer sur cette page via un fichier de type Uri
par opposition à un Object
a provoqué l'apparition du crash. Cela implique que l'objet initial doit être alloué différemment à cause de la méthode de navigation que j'ai utilisée. Étant donné qu'avec mon ancienne méthode de navigation, la mémoire était simplement empilée et laissée à ma disposition pour l'éternité, il ne semblait pas important qu'elle soit un peu corrompue à un ou deux petits endroits. Dès que le ramasseur d'ordures devait faire quelque chose avec cette mémoire (comme la nettoyer), il détectait la corruption de la mémoire et lançait l'exception. Ironiquement, ma fuite de mémoire majeure couvrait une erreur de mémoire fatale !
Il est évident que nous allons revoir cette interface pour éviter que des hypothèses aussi simples ne provoquent de tels crashs à l'avenir. J'espère que cela aidera d'autres personnes à comprendre ce qui se passe dans leur propre code.
0 votes
Avez-vous essayé de l'exécuter sous .NET 4.0 ? Bien que ce ne soit pas une solution, c'est un autre point de données.
0 votes
@Eamon : merci, c'est une bonne idée que je vais essayer.
1 votes
Pouvez-vous limiter le problème à certains composants/classes ? Si oui, vous pouvez peut-être obtenir au moins le code problématique en ajoutant les sorties de la position réelle à un journal de débogage.
0 votes
En supposant que le problème est une corruption de la mémoire, vous pouvez essayer de télécharger pageheap.exe de Microsoft et voir s'il y a des problèmes.
0 votes
Le plus que j'ai pu isoler, en commentant systématiquement des portions de code, c'est que l'erreur ne se produit que lorsque les appels à HttpWebRequest sont exécutés. Si je conserve toutes les fonctionnalités de threading et de parsing mais que je ne télécharge pas de nouvelles données, l'erreur ne se produit pas. En fait, j'ai essayé de déterminer si le problème était dû à ma classe de threads (comme les appels entre threads) ou à la classe d'analyse des données. L'erreur ne se produit que si le téléchargement est activé, et encore, seulement une fois par semaine environ (le programme fonctionne 24 heures sur 24, 7 jours sur 7). C'est difficile à reproduire !
0 votes
Avez-vous essayé d'ajouter plus de journalisation autour de vos zones de problèmes possibles via les classes de journalisation EnterpriseLibrary de MS ? J'ai eu un problème similaire il y a quelque temps, et l'ajout d'une journalisation avant et après chaque point de défaillance possible m'a permis de trouver la solution (qui n'avait rien à voir avec ce que je pensais ou avec ce que l'exception semblait être).
0 votes
@Jason M : Je n'utilise pas cette classe de journalisation particulière, non ; merci pour la suggestion.