about_Thread_Jobs

Kurze Beschreibung

Stellt Informationen zu Thread-basierten PowerShell-Aufträgen bereit. Ein Threadauftrag ist ein Hintergrundauftragstyp, 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 in 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 Aufwand. Sie verwenden nicht die Remotingschicht oder Serialisierung. Die Ergebnisobjekte werden als Verweise auf Liveobjekte in der aktuellen Sitzung zurückgegeben. Ohne diesen Aufwand werden threadbasierte Aufträge schneller ausgeführt und verwenden weniger Ressourcen als die anderen Auftragstypen.

Wichtig

Die übergeordnete Sitzung, die den Auftrag erstellt hat, überwacht auch den Auftragsstatus und sammelt Pipelinedaten. Der untergeordnete Auftragsvorgang wird vom übergeordneten Prozess beendet, sobald der Auftrag einen abgeschlossenen Zustand 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. Dient Invoke-Command zum Erstellen von Aufträgen, die in getrennten Sitzungen ausgeführt werden. Weitere Informationen finden Sie unter about_Remote_Jobs.
  2. Wird Start-Process verwendet, um einen neuen Prozess anstelle eines Auftrags zu erstellen. Weitere Informationen finden Sie unter Start-Process.

So starten und verwalten Sie threadbasierte 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 die in about_Jobs beschriebenen Cmdlets "Auftrag" zum Verwalten von threadbasierten Aufträgen.

Verwenden von Start-ThreadJob

Das ThreadJob-Modul wurde zuerst mit PowerShell 6 ausgeliefert. Sie 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 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 des aktuellen Ausführungsstatus. Es sammelt die Ergebnisse des Auftrags, während die Ergebnisse generiert werden.

Verwenden von ForEach-Object -Parallel -AsJob

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

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

Der folgende Befehl startet einen Auftrag, der untergeordnete Aufträge für jeden Eingabewert enthält, der an den Befehl weitergeleitet wird. Jeder untergeordnete Auftrag führt den Write-Output Befehl mit einem Weitergeleiteten 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 Weitergeleiteten Eingabewert enthält. Das Auftragsobjekt enthält nützliche Informationen zu den untergeordneten Aufträgen, die ausgeführt werden. Es sammelt 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 warten, bis ein Auftrag abgeschlossen ist, und dann alle Vom Auftrag generierten Ergebnisse zurückgeben.

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

Der folgende Befehl startet einen Auftrag, der einen Write-Output Befehl für jede weitergeleitete Eingabe ausführt, dann wartet, bis alle untergeordneten Aufträge abgeschlossen sind, und gibt schließlich alle Datenergebnisse zurück, die von den untergeordneten Aufträgen generiert wurden.

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, wird 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 zur Arbeit, die der Job tut, groß sein kann.

PowerShell führt Befehle und Skripts in einer Sitzung aus. In einer Sitzung kann jeweils nur ein Befehl oder Skript 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 Aufwand bei.

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

  • Die Arbeit ist rechenintensiv– Das Ausführen eines Skripts auf mehreren Threadaufträgen kann mehrere Prozessorkerne nutzen und schneller abschließen.

  • Die Arbeit besteht aus erheblichen Wartezeiten – ein Skript, das Zeit mit dem Warten auf E/A- oder Remoteanrufergebnisse verbringt. Die Parallelausfü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 Beispiel oben zeigt eine Foreachschleife, die 1000 Threadaufträge erstellt, um einen einfachen Zeichenfolgenschreibvorgang zu erstellen. Aufgrund des Auftragsaufwands dauert es mehr als 36 Sekunden, bis er abgeschlossen ist.

Im zweiten Beispiel wird das ForEach Cmdlet ausgeführt, um dieselben 1000 Vorgänge auszuführen. Diese Zeit ForEach-Object wird sequenziell auf einem einzelnen Thread ausgeführt, ohne dass ein Auftragsaufwand erforderlich ist. Es wird in nur 7 Millisekunden fertiggestellt.

Im folgenden Beispiel werden bis zu 5000 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 threadbasierte Aufträge zu übergeben.

Start-ThreadJob kann Variablen akzeptieren, die an das Cmdlet weitergeleitet werden, über das $using Schlüsselwort an den 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 -Parallel akzeptiert variablen und Variablen, die über das $using Schlüsselwort direkt an den Skriptblock übergeben werden.

$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 variable Verweistyp, der an den Auftrag übergeben wird, sorgfältig behandelt werden. Wenn es sich nicht um ein threadsicheres Objekt handelt, sollte es nie zugewiesen werden, und Methoden und Eigenschaften sollten niemals darauf 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

Siehe auch