2012-03-07 2 views
4

J'ai une application gui, qui montre périodiquement la charge du processeur. La charge est lue par une classe StateReader:Comment éviter la course sur le nettoyage RCW

public class StateReader 
{ 
    ManagementObjectSearcher searcher; 

    public StateReader() 
    { 
     ManagementScope scope = new ManagementScope("\\\\localhost\\root\\cimv2"); 
     ObjectQuery query = new ObjectQuery("select Name,PercentProcessorTime from Win32_PerfFormattedData_PerfOS_Processor where not Name='_Total'"); 
     searcher = new ManagementObjectSearcher(scope, query); 
    } 

    // give the maximum load over all cores 
    public UInt64 CPULoad() 
    { 
     List<UInt64> list = new List<UInt64>(); 
     ManagementObjectCollection results = searcher.Get(); 
     foreach (ManagementObject result in results) 
     { 
      list.Add((UInt64)result.Properties["PercentProcessorTime"].Value); 
     } 
     return list.Max(); 
    } 
} 

L'IUG est mise à jour en utilisant les extensions réactives:

var gui = new GUI(); 
var reader = new StateReader(); 

var sub = Observable.Interval(TimeSpan.FromSeconds(0.5)) 
        .Select(_ => reader.CPULoad()) 
        .ObserveOn(gui) 
        .Subscribe(gui.ShowCPUState); 

Application.Run(gui); 
sub.Dispose(); 

Maintenant, quand je quitte ma demande, je reçois une erreur disant

RaceOnRCWCleanup was detected. 
An attempt has been mad to free an RCW that is in use. The RCW is use on the 
active thread or another thread. Attempting to free an in-use RCW can cause 
corruption or data loss. 

Cette erreur n'apparaît pas si je ne lis pas la charge du processeur, mais fournissez simplement une valeur aléatoire, donc l'erreur est en quelque sorte liée à la lecture de la charge. Aussi, si je mets un point d'arrêt après Application.Run(gui) et attendez un peu, l'erreur ne semble pas venir aussi souvent. De là et de mon googling je pense que l'utilisation des classes dans l'espace de noms de gestion crée un thread d'arrière-plan qui référence un objet COM enveloppé dans un Runtime Callable Wrapper, et quand je quitte mon application, ce thread n'a pas le temps fermer correctement le RCW, conduisant à mon erreur. Est-ce correct, et comment puis-je résoudre ce problème?


J'ai modifié mon code pour refléter les réponses que j'ai, mais j'ai toujours la même erreur. Le code est mis à jour sur trois points:

  • StateReader est à usage unique, dispose de son ManagementObjectSearcher dans la méthode Dispose et j'appeler Dispose sur l'objet StateReader après Application.Run dans ma principale méthode
  • Dans CPULoad Je DEBARRASSER le ManagementCollection et chaque ManagementObject
  • Dans ma méthode principale, je dispose de l'objet d'abonnement dans un gestionnaire d'événements sur FormClosing
    sur l'interface graphique. Cela devrait garantir qu'aucun événement n'est généré pour l'interface graphique après sa fermeture.

Les parties pertinentes du code sont maintenant, en StateReader:

// give the maximum load over all cores 
public UInt64 CPULoad() 
{ 
    List<UInt64> list = new List<UInt64>(); 
    using (ManagementObjectCollection results = searcher.Get()) 
    { 
     foreach (ManagementObject result in results) 
     { 
      list.Add((UInt64)result.Properties["PercentProcessorTime"].Value); 
      result.Dispose(); 
     } 
    } 
    return list.Max(); 
} 

public void Dispose() 
{ 
    searcher.Dispose(); 
} 

Et dans mon principal:

gui.FormClosing += (a1, a2) => sub.Dispose(); 

Application.Run(gui); 
reader.Dispose(); 

Y at-il autre chose que je pouvais faire pour éviter l'erreur que je reçois ?

+1

Votre diagnostic est correct. Ce n'est pas le seul problème, l'appel .ObserveOn (gui) est très gênant aussi. Vous * devez * vous assurer qu'aucune autre notification ne peut être générée avant d'autoriser la fermeture du formulaire. Tels sont les risques de laisser courir les fils. –

+0

@Hans Passant: J'ai modifié mon code pour disposer de l'abonnement sur FormClosing. Diriez-vous que cela résout le problème que vous avez mentionné - je n'ai pas d'autres événements sur le formulaire, autres que des événements provenant de l'utilisateur qui interagit avec lui, comme des clics de bouton et autres. – Boris

+1

Probablement pas, c'est une course de threads si un thread TP est planifié lorsque vous appelez Dispose() mais n'a pas encore commencé à s'exécuter. Je ne connais pas assez bien la plomberie réactive. –

Répondre

1

Je pense que vous devez mettre StateReader jetable et le jeter avant de quitter votre application. StateReader doit disposer searcher. Cependant, je pense que le vrai problème est que vous ne disposez pas ManagementObject en CPULoad. Si GC s'exécute après CPULoad les RCW seront libérés. Cependant, si vous quittez avant GC, cela peut déclencher l'exception que vous voyez.

Je pense que l'utilisation des classes dans l'espace de gestion crée un thread d'arrière-plan qui fait référence à un objet COM enveloppé dans un Runtime Callable Wrapper

Observable.Interval crée un thread d'arrière-plan et CPULoad exécute sur ce thread.

+1

Merci, j'ai oublié le fil de fond de Observable.Interval. J'ai modifié mon code pour refléter vos suggestions, mais j'ai toujours l'erreur. – Boris

0

Ne laissez pas l'application quitter pendant que le thread d'arrière-plan exécute CPULoad pour l'éviter.

La solution la plus simple que je peux trouver est de charger le CPU à partir d'un nouveau thread de premier plan, puis de rejoindre les threads.

public UInt64 CPULoad() 
{ 
    List<UInt64> list = new List<UInt64>(); 
    Thread thread = new Thread(() => 
    { 
     ManagementObjectCollection results = searcher.Get(); 
     foreach (ManagementObject result in results) 
     { 
      list.Add((UInt64)result.Properties["PercentProcessorTime"].Value); 
     } 
    }); 
    thread.Start(); 
    thread.Join(); 
    return list.Max(); 
} 

Le temps de démarrage d'un nouveau thread à chaque fois est négligeable par rapport aux appels WMI lents. Le temporisateur synchrone mentionné dans les commentaires obtient pratiquement le même comportement, mais il bloque le thread UI et est difficilement utilisable avec WMI lent.

Questions connexes