Update - Während diese Antwort den Prozess und die Funktionsweise von PowerShell-Runspaces erläutert und erläutert, wie sie Ihnen helfen können, nicht sequenzielle Workloads für mehrere Threads zu erstellen, hat ein PowerShell-Fan, Warren 'Cookie Monster' F , die Extrameile auf sich genommen und dieselben Konzepte in ein einziges Tool integriert genannt - es macht das, was ich unten beschreibe, und er hat es seitdem mit optionalen Schaltern für die Protokollierung und den vorbereiteten Sitzungsstatus erweitert, einschließlich importierter Module, wirklich coole Sachen - ich empfehle dringend, dass Sie es ausprobieren, bevor Sie Ihre eigene glänzende Lösung erstellen!Invoke-Parallel
Bei paralleler Ausführung von Runspace:
Verkürzung der unausweichlichen Wartezeit
Im ursprünglichen speziellen Fall hat die aufgerufene ausführbare Datei ein /nowait
Option, die verhindert, dass der aufrufende Thread blockiert wird, während der Auftrag (in diesem Fall die erneute Zeitsynchronisierung) von selbst abgeschlossen wird.
Dies reduziert die Gesamtausführungszeit aus Sicht des Emittenten erheblich, die Verbindung zu jedem Computer wird jedoch nacheinander hergestellt. Das Herstellen einer Verbindung zu Tausenden von Clients in Folge kann je nach Anzahl der Computer, auf die aus dem einen oder anderen Grund aufgrund einer Häufung von Wartezeiten aufgrund von Zeitüberschreitungen nicht zugegriffen werden kann, einige Zeit in Anspruch nehmen.
Um zu vermeiden, dass bei einem oder mehreren aufeinanderfolgenden Timeouts alle nachfolgenden Verbindungen in die Warteschlange gestellt werden müssen, können wir den Auftrag zum Verbinden und Aufrufen von Befehlen an separate PowerShell-Runspaces weiterleiten und parallel ausführen.
Was ist ein Runspace?
Ein Runspace ist der virtuelle Container, in dem Ihr Powershell-Code ausgeführt wird und der die Umgebung aus der Perspektive einer PowerShell-Anweisung / eines PowerShell-Befehls darstellt / enthält.
Allgemein ausgedrückt ist 1 Runspace = 1 Ausführungsthread. Alles, was wir zum "Multithreading" unseres PowerShell-Skripts benötigen, ist eine Sammlung von Runspaces, die dann wiederum parallel ausgeführt werden können.
Wie beim ursprünglichen Problem kann der Job zum Aufrufen von Befehlen in mehrere Runspaces unterteilt werden in:
- Einen RunspacePool erstellen
- Zuweisen eines PowerShell-Skripts oder eines entsprechenden ausführbaren Codes zum RunspacePool
- Rufen Sie den Code asynchron auf (dh Sie müssen nicht auf die Rückkehr des Codes warten)
RunspacePool-Vorlage
In PowerShell gibt es einen Typbeschleuniger namens [RunspaceFactory]
Typbeschleuniger, der uns bei der Erstellung von Runspace-Komponenten unterstützt
1. Erstellen Sie einen RunspacePool und Open()
es:
$RunspacePool = [runspacefactory]::CreateRunspacePool(1,8)
$RunspacePool.Open()
Die beiden Argumente, die an übergeben wurden CreateRunspacePool()
, 1
und 8
sind die minimale und maximale Anzahl von Runspaces, die zu einem bestimmten Zeitpunkt ausgeführt werden dürfen. Dies ergibt einen effektiven maximalen Parallelitätsgrad von 8.
2. Erstellen Sie eine Instanz von PowerShell, hängen Sie einen ausführbaren Code an und weisen Sie ihn unserem RunspacePool zu:
Eine Instanz von PowerShell ist nicht dasselbe wie der powershell.exe
Prozess (der eigentlich eine Host-Anwendung ist), sondern ein internes Laufzeitobjekt, das den auszuführenden PowerShell-Code darstellt. Mit dem Typbeschleuniger können Sie [powershell]
eine neue PowerShell-Instanz in PowerShell erstellen:
$Code = {
param($Credentials,$ComputerName)
$session = New-PSSession -ComputerName $ComputerName -Credential $Credentials
Invoke-Command -Session $session -ScriptBlock {w32tm /resync /nowait /rediscover}
}
$PSinstance = [powershell]::Create().AddScript($Code).AddArgument($creds).AddArgument("computer1.domain.tld")
$PSinstance.RunspacePool = $RunspacePool
3. Rufen Sie die PowerShell-Instanz asynchron mit APM auf:
Unter Verwendung der in der .NET-Entwicklungsterminologie als asynchrones Programmiermodell bekannten Begin
Methode können wir den Aufruf eines Befehls in eine Methode aufteilen , um grünes Licht für die Ausführung des Codes zu geben, und eine End
Methode zum Sammeln der Ergebnisse. Da wir in diesem Fall nicht wirklich an Feedback interessiert sind (wir warten w32tm
sowieso nicht auf die Ausgabe von ), können wir dies durch einfachen Aufruf der ersten Methode erreichen
$PSinstance.BeginInvoke()
Einpacken in einen RunspacePool
Mit der obigen Technik können wir die sequentiellen Iterationen des Erstellens neuer Verbindungen und des Aufrufs des Remote-Befehls in einem parallelen Ausführungsablauf umbrechen:
$ComputerNames = Get-ADComputer -filter * -Properties dnsHostName |select -Expand dnsHostName
$Code = {
param($Credentials,$ComputerName)
$session = New-PSSession -ComputerName $ComputerName -Credential $Credentials
Invoke-Command -Session $session -ScriptBlock {w32tm /resync /nowait /rediscover}
}
$creds = Get-Credential domain\user
$rsPool = [runspacefactory]::CreateRunspacePool(1,8)
$rsPool.Open()
foreach($ComputerName in $ComputerNames)
{
$PSinstance = [powershell]::Create().AddScript($Code).AddArgument($creds).AddArgument($ComputerName)
$PSinstance.RunspacePool = $rsPool
$PSinstance.BeginInvoke()
}
Unter der Annahme, dass die CPU die Kapazität hat, alle 8 Runspaces gleichzeitig auszuführen, sollten wir feststellen können, dass sich die Ausführungszeit erheblich verkürzt, jedoch auf Kosten der Lesbarkeit des Skripts aufgrund der eher "fortgeschrittenen" verwendeten Methoden.
Ermittlung des optimalen Parallismusgrades:
Wir könnten leicht einen RunspacePool erstellen, der die gleichzeitige Ausführung von 100 Runspaces ermöglicht:
[runspacefactory]::CreateRunspacePool(1,100)
Letztendlich kommt es jedoch darauf an, wie viele Ausführungseinheiten unsere lokale CPU verarbeiten kann. Mit anderen Worten, solange Ihr Code ausgeführt wird, ist es nicht sinnvoll, mehr Runspaces zuzulassen, als Sie über logische Prozessoren verfügen, an die die Ausführung des Codes gesendet werden kann.
Dank WMI ist dieser Schwellenwert recht einfach zu bestimmen:
$NumberOfLogicalProcessor = (Get-WmiObject Win32_Processor).NumberOfLogicalProcessors
[runspacefactory]::CreateRunspacePool(1,$NumberOfLogicalProcessors)
Wenn andererseits der Code, den Sie selbst ausführen, aufgrund externer Faktoren wie der Netzwerklatenz viel Wartezeit verursacht, können Sie trotzdem davon profitieren, dass mehr Runspaces gleichzeitig ausgeführt werden, als über logische Prozessoren verfügen. Daher möchten Sie wahrscheinlich testen von Bereich möglichen maximalen Runspaces, um die Gewinnschwelle zu finden :
foreach($n in ($NumberOfLogicalProcessors..($NumberOfLogicalProcessors*3)))
{
Write-Host "$n: " -NoNewLine
(Measure-Command {
$Computers = Get-ADComputer -filter * -Properties dnsHostName |select -Expand dnsHostName -First 100
...
[runspacefactory]::CreateRunspacePool(1,$n)
...
}).TotalSeconds
}