11

Ho creato uno script PowerShell che scorre su un numero elevato di file XML Schema (.xsd) e crea ciascuno un oggetto .NET XmlSchemaSet, chiama Add() e Compile() per aggiungervi uno schema e stampa tutti gli errori di convalida.Come istruire PowerShell a raccogliere oggetti .NET come XmlSchemaSet?

Questo script funziona correttamente, ma c'è una perdita di memoria da qualche parte, causando il consumo di gigabyte di memoria se eseguito su centinaia di file.

Quello che in sostanza fare in un ciclo è la seguente:

$schemaSet = new-object -typename System.Xml.Schema.XmlSchemaSet 
register-objectevent $schemaSet ValidationEventHandler -Action { 
    ...write-host the event details... 
} 
$reader = [System.Xml.XmlReader]::Create($schemaFileName) 
[void] $schemaSet.Add($null_for_dotnet_string, $reader) 
$reader.Close() 
$schemaSet.Compile() 

(uno script completo per riprodurre il problema può essere trovato in questo gist:. https://gist.github.com/3002649 Basta eseguirlo, e guardare l'aumento dell'utilizzo della memoria in Task manager o Process Explorer.)

Ispirato da alcuni post del blog, ho provato ad aggiungere

remove-variable reader, schemaSet 

ho provato anche raccogliendo la $schema fro m Add() e facendo

[void] $schemaSet.RemoveRecursive($schema) 

Questi sembrano avere qualche effetto, ma ancora non v'è una perdita. Presumo che le precedenti istanze di XmlSchemaSet utilizzino ancora la memoria senza essere raccolte in modo inutile.

La domanda: Come faccio a insegnare correttamente al garbage collector che può recuperare tutta la memoria utilizzata nel codice sopra? O più in generale: come posso raggiungere il mio obiettivo con una quantità limitata di memoria?

risposta

9

Microsoft ha confermato che questo è un bug in PowerShell 2.0, e affermare che questo è stato risolto in PowerShell 3.0.

Il problema è che un gestore di eventi registrato con Register-ObjectEvent non è garbage collector. In reponse ad una chiamata di supporto, Microsoft ha detto che

"Abbiamo a che fare con un bug in PowerShell v.2. Il problema è causato in realtà dal fatto che le istanze di oggetti .NET non sono più rilasciato sono dovuto al fatto che i gestori degli eventi non vengono rilasciati autonomamente. Il problema non è più riproducibile con PowerShell v.3 ".

La soluzione migliore, per quanto posso vedere, è quello di interfaccia tra PowerShell e .NET a un livello diverso: fare completamente la validazione in codice C# (incorporato nello script PowerShell), e basta passare indietro un elenco degli oggetti ValidationEventArgs. Vedi lo script di riproduzione fisso su https://gist.github.com/3697081: quello script è funzionalmente corretto e non perde memoria.

(Grazie all'assistenza Microsoft per avermi aiutato a trovare questa soluzione.)


Inizialmente Microsoft ha offerto un'altra soluzione, che è quello di utilizzare $xyzzy = Register-ObjectEvent -SourceIdentifier XYZZY, e poi alla fine fare quanto segue:

Unregister-Event XYZZY 
Remove-Job $xyzzy -Force 

Tuttavia, questa soluzione è funzionalmente corretta. Tutti gli eventi che sono ancora "in volo" vengono persi nel momento in cui vengono eseguite queste due istruzioni aggiuntive. Nel mio caso, ciò significa che mi mancano errori di validazione, quindi l'output del mio script è incompleto.

4

Dopo la remove-variable si può provare a forzare collezione GC:

[GC]::Collect() 
+0

Questo rende l'aumento inferiore, quindi questo può essere d'aiuto nella pratica; ma la quantità di memoria utilizzata sta ancora aumentando gradualmente. –

+1

aggiungendo un '$ reader.Dispose()' dopo averlo chiuso aiuta di più? –

+0

Come descritto in [un'altra risposta StackOverflow] (http://stackoverflow.com/a/745965/223837) questo non è direttamente possibile in PowerShell e non è necessario perché 'Chiudi()' implica un 'Dispose()' come da convenzione Microsoft raccomandata. –