PowerShell 7: Parallele Ausführung mit ForEach-Object -parallel

Der Dotnet-Doktor  –  0 Kommentare

Seit PowerShell 7.0 kann man mit dem Parameter -parallel die Ausführung der Schleifendurchläufe auf verschiedene Threads parallelisieren (via Multithreading).

Im ersten Teil dieser Blogserie habe ich die technische Basis für die PowerShell 7.0 betrachtet, im zweiten Teil den aktuellen Funktionsumfang der PowerShell 7.0 im Vergleich zur PowerShell Core 6.x und Windows PowerShell 5.1 quantifiziert. Nun möchte ich damit beginnen, einzelne neue Funktionen vorzustellen. Dabei geht es hier um den mächtigen neuen Parameter -parallel im altbekannten Commandlet Foreach-Object.

In PowerShell 1.0 bis 6.2 erfolgt die Ausführung von Foreach-Object im Haupt-Thread der PowerShell, das heißt, die einzelnen Durchläufe erfolgen nacheinander. Seit PowerShell 7.0 kann man mit dem Parameter -parallel die Ausführung auf verschiedene Threads parallelisieren (via Multithreading), sodass bei längeren Operationen in Summe das Ergebnis schneller vorliegt.

Multithreading hat immer einigen Overhead. Die Parallelisierung lohnt sich nur bei länger dauernden Operationen. Bei kurzen Operationen ist der Zeitverlust durch das Erzeugen und Vernichten der Threads höher als der Zeitgewinn durch die Parallelisierung.

Das folgende Beispiel zeigt zwei Varianten der Abfrage, ob die Software "Classic Shell" auf drei verschiedenen Computern installiert ist. Bei der ersten Variante ohne -parallel wird die leider etwas langwierige Abfrage der WMI-Klasse Win32_Product auf den drei Computern nacheinander im gleichen Thread ausgeführt. Bei der zweiten Variante mit -parallel wird die Abfrage parallel in drei verschiedenen Threads gestartet; die Ergebnisse kommen erst rein, nachdem alle Anfragen raus sind (s. Abb. unten). Dass die Ausführung tatsächlich in verschiedene Threads erfolgt, wird bewiesen unter Abfrage der ManagedThreadId im aktuellen Thread aus der .NET-Klasse Thread: [System.Threading.Thread]::CurrentThread.ManagedThreadId.

Write-Host "# ForEach-Object ohne -parallel" -ForegroundColor Yellow
"E27","E29","E44" | ForEach-Object {
"Abfrage bei Computer $_ in Thread $([System.Threading.Thread]::CurrentThread.ManagedThreadId)"
$e = Get-CimInstance -Class Win32_Product -Filter "Name='Classic Shell'" -computername $_
if ($e -eq $null) { "Kein Ergebnis bei $_!"}
else { $e }
}
Write-host ""
Write-host " # ForEach-Object mit -parallel" -ForegroundColor Yellow
"E27","E29","E44" | ForEach-Object -parallel {
"Abfrage bei Computer $_ in Thread $([System.Threading.Thread]::CurrentThread.ManagedThreadId)"
$e = Get-CimInstance -Class Win32_Product -Filter "Name='Classic Shell'" -computername $_
if ($e -eq $null) { "Kein Ergebnis bei $_!"}
else { $e }
}
# ohne Read-Host würde das Skript die später eingehenden Ergebnisse nicht mehr anzeigen!
read-host
Parallelität bei Foreach-Object in PowerShell 7

Die Anzahl der Threads, die Foreach-Object nutzen soll, kann man mit dem Parameter -ThrottleLimit begrenzen:

1..20 | ForEach-Object -parallel { 
Write-host "Objekt #$_ in Thread $([System.Threading.Thread]::CurrentThread.ManagedThreadId)"
sleep -Seconds 2 } -ThrottleLimit 5