Freigeben über


about_Thread_Jobs

Kurze Beschreibung

Stellt Informationen zu Thread-basierten PowerShell-Aufträgen bereit. Ein Threadauftrag ist ein Typ von Hintergrundauftrag, der einen Befehl oder Ausdruck in einem separaten Thread innerhalb des aktuellen Sitzungsprozesses ausführt.

Lange Beschreibung

PowerShell führt Befehle und Skripts gleichzeitig über Aufträge aus. Es gibt drei Auftragstypen, die von PowerShell bereitgestellt werden, um Parallelität zu unterstützen.

  • RemoteJob – Befehle und Skripts werden in einer Remotesitzung ausgeführt. Weitere Informationen finden Sie unter about_Remote_Jobs.
  • BackgroundJob – Befehle und Skripts werden in einem separaten Prozess auf dem lokalen Computer ausgeführt. Weitere Informationen finden Sie unter about_Jobs.
  • PSTaskJob oder ThreadJob : Befehle und Skripts werden in einem separaten Thread innerhalb desselben Prozesses auf dem lokalen Computer ausgeführt.

Threadbasierte Aufträge sind nicht so robust wie Remote- und Hintergrundaufträge, da sie im selben Prozess auf verschiedenen Threads ausgeführt werden. Wenn ein Auftrag einen kritischen Fehler aufweist, der den Prozess abstürzt, werden alle anderen Aufträge im Prozess beendet.

Threadbasierte Aufträge erfordern jedoch weniger Mehraufwand. Sie verwenden weder die Remotingebene noch die Serialisierung. Die Ergebnisobjekte werden als Verweise auf Liveobjekte in der aktuellen Sitzung zurückgegeben. Ohne diesen Mehraufwand werden threadbasierte Aufträge schneller ausgeführt und verbrauchen weniger Ressourcen als die anderen Auftragstypen.

Wichtig

Die übergeordnete Sitzung, die den Auftrag erstellt hat, überwacht auch den Auftrag status und sammelt Pipelinedaten. Der untergeordnete Auftragsprozess wird vom übergeordneten Prozess beendet, sobald der Auftrag den Status "Abgeschlossen" erreicht hat. Wenn die übergeordnete Sitzung beendet wird, werden alle ausgeführten untergeordneten Aufträge zusammen mit ihren untergeordneten Prozessen beendet.

Es gibt zwei Möglichkeiten, diese Situation zu umgehen:

  1. Verwenden Sie Invoke-Command zum Erstellen von Aufträgen, die in getrennten Sitzungen ausgeführt werden. Weitere Informationen finden Sie unter about_Remote_Jobs.
  2. Verwenden Sie Start-Process , um einen neuen Prozess anstelle eines Auftrags zu erstellen. Weitere Informationen finden Sie unter Start-Process.

Starten und Verwalten threadbasierter Aufträge

Es gibt zwei Möglichkeiten, threadbasierte Aufträge zu starten:

  • Start-ThreadJob– aus dem ThreadJob-Modul
  • ForEach-Object -Parallel -AsJob – Das parallele Feature wurde in PowerShell 7.0 hinzugefügt.

Verwenden Sie dieselben Auftrags-Cmdlets , die unter about_Jobs beschrieben sind, um threadbasierte Aufträge zu verwalten.

Verwenden von Start-ThreadJob

Das ThreadJob-Modul wurde zuerst mit PowerShell 6 ausgeliefert. Es kann auch über die PowerShell-Katalog für Windows PowerShell 5.1 installiert werden.

Um einen Threadauftrag auf dem lokalen Computer zu starten, verwenden Sie das Cmdlet mit einem Befehl oder Skript, der Start-ThreadJob in geschweifte Klammern ({ }) eingeschlossen ist.

Im folgenden Beispiel wird ein Threadauftrag gestartet, der einen Get-Process Befehl auf dem lokalen Computer ausführt.

Start-ThreadJob -ScriptBlock { Get-Process }

Der Start-ThreadJob Befehl gibt ein ThreadJob Objekt zurück, das den ausgeführten Auftrag darstellt. Das Auftragsobjekt enthält nützliche Informationen zum Auftrag, einschließlich seiner aktuell ausgeführten status. Es erfasst die Ergebnisse des Auftrags, während die Ergebnisse generiert werden.

Verwenden von ForEach-Object -Parallel -AsJob

PowerShell 7.0 hat dem ForEach-Object Cmdlet einen neuen Parametersatz hinzugefügt. Mit den neuen Parametern können Sie Skriptblöcke in parallelen Threads als PowerShell-Aufträge ausführen.

Sie können Daten an übergeben ForEach-Object -Parallel. Die Daten werden an den Skriptblock übergeben, der parallel ausgeführt wird. Der -AsJob Parameter erstellt Auftragsobjekte für jeden parallelen Threads.

Mit dem folgenden Befehl wird ein Auftrag gestartet, der untergeordnete Aufträge für jeden Eingabewert enthält, der an den Befehl übergeben wird. Jeder untergeordnete Auftrag führt den Write-Output Befehl mit einem Pipe-Eingabewert als Argument aus.

1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob

Der ForEach-Object -Parallel Befehl gibt ein PSTaskJob Objekt zurück, das untergeordnete Aufträge für jeden übergebenen Eingabewert enthält. Das Auftragsobjekt enthält nützliche Informationen zu den untergeordneten Aufträgen, die status ausgeführt werden. Es erfasst die Ergebnisse der untergeordneten Aufträge, während die Ergebnisse generiert werden.

Warten auf den Abschluss eines Auftrags und Abrufen von Auftragsergebnissen

Sie können PowerShell-Auftrags-Cmdlets verwenden, z Wait-Job . B. und Receive-Job , um auf den Abschluss eines Auftrags zu warten und dann alle vom Auftrag generierten Ergebnisse zurückzugeben.

Der folgende Befehl startet einen Threadauftrag, der einen Get-Process Befehl ausführt, wartet dann, bis der Befehl abgeschlossen ist, und gibt schließlich alle vom Befehl generierten Datenergebnisse zurück.

Start-ThreadJob -ScriptBlock { Get-Process } | Wait-Job | Receive-Job

Mit dem folgenden Befehl wird ein Auftrag gestartet, der einen Write-Output Befehl für jede übergebene Eingabe ausführt, dann wartet, bis alle untergeordneten Aufträge abgeschlossen sind, und gibt schließlich alle von den untergeordneten Aufträgen generierten Datenergebnisse zurück.

1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob | Wait-Job | Receive-Job

Das Receive-Job Cmdlet gibt die Ergebnisse der untergeordneten Aufträge zurück.

1
3
2
4
5

Da jeder untergeordnete Auftrag parallel ausgeführt wird, ist die Reihenfolge der generierten Ergebnisse nicht garantiert.

Threadauftragsleistung

Threadaufträge sind schneller und leichter als andere Arten von Aufträgen. Aber sie haben immer noch Mehraufwand, der im Vergleich zu der Arbeit, die der Auftrag ausführt, groß sein kann.

PowerShell führt Befehle und Skripts in einer Sitzung aus. Nur ein Befehl oder Skript kann gleichzeitig in einer Sitzung ausgeführt werden. Wenn Sie also mehrere Aufträge ausführen, wird jeder Auftrag in einer separaten Sitzung ausgeführt. Jede Sitzung trägt zum Mehraufwand bei.

Threadaufträge bieten die beste Leistung, wenn die von ihnen ausgeführte Arbeit größer ist als der Mehraufwand der Sitzung, die zum Ausführen des Auftrags verwendet wird. Es gibt zwei Fälle, für die diese Kriterien erfüllt werden.

  • Die Arbeit ist rechenintensiv: Das Ausführen eines Skripts für mehrere Threadaufträge kann mehrere Prozessorkerne nutzen und schneller abgeschlossen werden.

  • Die Arbeit besteht aus erheblichem Warten: Ein Skript, das Zeit damit verbringt, auf E/A- oder Remoteaufrufergebnisse zu warten. Die parallele Ausführung erfolgt in der Regel schneller als bei sequenzieller Ausführung.

(Measure-Command {
    1..1000 | ForEach { Start-ThreadJob { Write-Output "Hello $using:_" } } | Receive-Job -Wait
}).TotalMilliseconds
36860.8226

(Measure-Command {
    1..1000 | ForEach-Object { "Hello: $_" }
}).TotalMilliseconds
7.1975

Das erste obige Beispiel zeigt eine foreach-Schleife, die 1000 Threadaufträge erstellt, um einen einfachen Zeichenfolgenschreibvorgang auszuführen. Aufgrund des Auftragsaufwands dauert die Ausführung mehr als 36 Sekunden.

Im zweiten Beispiel wird das ForEach Cmdlet ausgeführt, um dieselben 1000-Vorgänge auszuführen. Dieses Mal ForEach-Object wird sequenziell in einem einzelnen Thread ohne Auftragsaufwand ausgeführt. Es wird in nur 7 Millisekunden abgeschlossen.

Im folgenden Beispiel werden bis zu 5.000 Einträge für 10 separate Systemprotokolle gesammelt. Da das Skript das Lesen einer Reihe von Protokollen umfasst, ist es sinnvoll, die Vorgänge parallel auszuführen.

$logNames.count
10

Measure-Command {
    $logs = $logNames | ForEach-Object {
        Get-WinEvent -LogName $_ -MaxEvents 5000 2>$null
    }
}

TotalMilliseconds : 252398.4321 (4 minutes 12 seconds)
$logs.Count
50000

Das Skript wird in der Hälfte der Zeit abgeschlossen, in der die Aufträge parallel ausgeführt werden.

Measure-Command {
    $logs = $logNames | ForEach {
        Start-ThreadJob {
            Get-WinEvent -LogName $using:_ -MaxEvents 5000 2>$null
        } -ThrottleLimit 10
    } | Wait-Job | Receive-Job
}

TotalMilliseconds : 115994.3 (1 minute 56 seconds)
$logs.Count
50000

Threadaufträge und Variablen

Es gibt mehrere Möglichkeiten, Werte an die threadbasierten Aufträge zu übergeben.

Start-ThreadJobkann Variablen akzeptieren, die an das Cmdlet übergeben, über den Schlüsselwort (keyword) an den $using Skriptblock übergeben oder über den ArgumentList-Parameter übergeben werden.

$msg = "Hello"

$msg | Start-ThreadJob { $input | Write-Output } | Wait-Job | Receive-Job

Start-ThreadJob { Write-Output $using:msg } | Wait-Job | Receive-Job

Start-ThreadJob { param ([string] $message) Write-Output $message } -ArgumentList @($msg) |
  Wait-Job | Receive-Job

ForEach-Object -Parallelakzeptiert Variablen, die als Pipe ausgeführt werden, und Variablen werden direkt über den Schlüsselwort (keyword) an den $using Skriptblock übergeben.

$msg = "Hello"

$msg | ForEach-Object -Parallel { Write-Output $_ } -AsJob | Wait-Job | Receive-Job

1..1 | ForEach-Object -Parallel { Write-Output $using:msg } -AsJob | Wait-Job | Receive-Job

Da Threadaufträge im selben Prozess ausgeführt werden, muss jeder an den Auftrag übergebene Variablenverweistyp sorgfältig behandelt werden. Wenn es sich nicht um ein threadsicheres Objekt handelt, sollte es niemals zugewiesen werden, und Methoden und Eigenschaften sollten nie aufgerufen werden.

Im folgenden Beispiel wird ein threadsicheres .NET-Objekt ConcurrentDictionary an alle untergeordneten Aufträge übergeben, um eindeutig benannte Prozessobjekte zu sammeln. Da es sich um ein threadsicheres Objekt handelt, kann es sicher verwendet werden, während die Aufträge gleichzeitig im Prozess ausgeführt werden.

$threadSafeDictionary = [System.Collections.Concurrent.ConcurrentDictionary[string,object]]::new()
$jobs = Get-Process | ForEach {
    Start-ThreadJob {
        $proc = $using:_
        $dict = $using:threadSafeDictionary
        $dict.TryAdd($proc.ProcessName, $proc)
    }
}
$jobs | Wait-Job | Receive-Job

$threadSafeDictionary.Count
96

$threadSafeDictionary["pwsh"]

NPM(K)  PM(M)   WS(M) CPU(s)    Id SI ProcessName
------  -----   ----- ------    -- -- -----------
  112  108.25  124.43  69.75 16272  1 pwsh

Weitere Informationen