Dans une bibliothèque que j'ai créée, j'ai une classe, DataPort, qui met en œuvre une fonctionnalité similaire à la classe SerialPort de .NET. Elle communique avec un certain matériel et déclenche un événement chaque fois que des données arrivent par ce matériel. Pour mettre en œuvre ce comportement, DataPort fait tourner un thread qui doit avoir la même durée de vie que l'objet DataPort. Le problème est que lorsque le DataPort sort du champ d'application, il ne sont jamais collectés
Maintenant, comme DataPort communique avec le matériel (en utilisant pInvoke) et possède certaines ressources non gérées, il implémente IDisposable. Lorsque vous appelez Dispose sur l'objet, tout se passe correctement. Le DataPort se débarrasse de toutes ses ressources non gérées, tue le worker thread et s'en va. Cependant, si vous laissez le DataPort sortir du champ d'application, le ramasseur d'ordures n'appellera jamais le finaliseur et le DataPort restera en mémoire pour toujours. Je sais que cela se produit pour deux raisons :
- Un point d'arrêt dans le finalisateur n'est jamais atteint.
- SOS.dll me dit que le DataPort est toujours en vie.
Sidebar : Avant d'aller plus loin, je dirai que oui, je sais que la réponse est "Appelez Dispose() Dummy !" mais je pense que même si vous laissez toutes les références sortir de la portée, la bonne chose devrait se produire éventuellement et le collecteur d'ordures devrait se débarrasser du DataPort
Retour à la question : En utilisant SOS.dll, je peux voir que la raison pour laquelle mon DataPort n'est pas collecté est que le thread qu'il a lancé a toujours une référence à l'objet DataPort - à travers le paramètre implicite "this" de la méthode d'instance que le thread exécute. Le fil de travail en cours d'exécution ne seront pas collectés par les ordures ménagères Par conséquent, toutes les références qui se trouvent dans le champ d'application du thread de travail en cours d'exécution ne sont pas non plus admissibles à la collecte des déchets.
Le thread lui-même exécute essentiellement le code suivant :
public void WorkerThreadMethod(object unused)
{
ManualResetEvent dataReady = pInvoke_SubcribeToEvent(this.nativeHardwareHandle);
for(;;)
{
//Wait here until we have data, or we got a signal to terminate the thread because we're being disposed
int signalIndex = WaitHandle.WaitAny(new WaitHandle[] {this.dataReady, this.closeSignal});
if(signalIndex == 1) //closeSignal is at index 1
{
//We got the close signal. We're being disposed!
return; //This will stop the thread
}
else
{
//Must've been the dataReady signal from the hardware and not the close signal.
this.ProcessDataFromHardware();
dataReady.Reset()
}
}
}
La méthode Dispose contient le code suivant (pertinent) :
public void Dispose()
{
closeSignal.Set();
workerThread.Join();
}
Parce que le thread est un Root gc et qu'il détient une référence au DataPort, le DataPort n'est jamais éligible à la collecte des déchets. Parce que le finaliseur n'est jamais appelé, nous n'envoyons jamais le signal de fermeture au thread de travail. Comme ce dernier ne reçoit jamais le signal de fermeture, il continue à maintenir la référence à l'infini. ACK !
La seule solution à laquelle je pense pour résoudre ce problème est de supprimer le paramètre "this" de la méthode WorkerThread (voir les réponses ci-dessous). Quelqu'un d'autre peut-il penser à une autre solution ? Il doit y avoir un meilleur moyen de créer un objet avec un thread qui a la même durée de vie que l'objet ! Ou bien, est-ce que cela peut être fait sans un thread séparé ? J'ai choisi cette conception particulière pour les raisons suivantes ce poste sur les forums msdn qui décrivent certains des détails de l'implémentation interne de la classe de port série .NET ordinaire
Mise à jour un peu d'informations supplémentaires dans les commentaires :
- Dans le fil de discussion en question, IsBackground est réglé sur true.
- Les ressources non gérées mentionnées ci-dessus n'affectent pas le problème. Même si tout dans l'exemple utilisait des ressources gérées, je verrais toujours le même problème.