about_Thread_Jobs
Krótki opis
Zawiera informacje o zadaniach opartych na wątkach programu PowerShell. Zadanie wątku jest typem zadania w tle, które uruchamia polecenie lub wyrażenie w osobnym wątku w ramach bieżącego procesu sesji.
Długi opis
Program PowerShell jednocześnie uruchamia polecenia i skrypty za pośrednictwem zadań. Program PowerShell udostępnia trzy typy zadań do obsługi współbieżności.
RemoteJob
- Polecenia i skrypty są uruchamiane w sesji zdalnej. Aby uzyskać informacje, zobacz about_Remote_Jobs.BackgroundJob
- Polecenia i skrypty są uruchamiane w osobnym procesie na komputerze lokalnym. Aby uzyskać więcej informacji, zobacz opis polecenia about_Jobs.PSTaskJob
lubThreadJob
— polecenia i skrypty są uruchamiane w osobnym wątku w ramach tego samego procesu na komputerze lokalnym.
Zadania oparte na wątkach nie są tak niezawodne jak zadania zdalne i w tle, ponieważ są uruchamiane w tym samym procesie w różnych wątkach. Jeśli jedno zadanie ma krytyczny błąd powodujący awarię procesu, wszystkie inne zadania w procesie zostaną zakończone.
Jednak zadania oparte na wątkach wymagają mniejszego obciążenia. Nie używają warstwy komunikacji zdalnej ani serializacji. Obiekty wynikowe są zwracane jako odwołania do obiektów na żywo w bieżącej sesji. Bez tego obciążenia zadania oparte na wątkach działają szybciej i używają mniej zasobów niż inne typy zadań.
Ważne
Sesja nadrzędna, która utworzyła zadanie, monitoruje również stan zadania i zbiera dane potoku. Proces podrzędny zadania zostaje zakończony przez proces nadrzędny po osiągnięciu stanu zakończenia zadania. Jeśli sesja nadrzędna zostanie zakończona, wszystkie uruchomione zadania podrzędne zostaną zakończone wraz z ich procesami podrzędnymi.
Istnieją dwa sposoby obejścia tej sytuacji:
- Służy
Invoke-Command
do tworzenia zadań uruchamianych w rozłączonych sesjach. Aby uzyskać więcej informacji, zobacz about_Remote_Jobs. - Użyj
Start-Process
polecenia , aby utworzyć nowy proces, a nie zadanie. Aby uzyskać więcej informacji, zobacz Rozpoczynanie procesu.
Jak uruchamiać zadania oparte na wątkach i zarządzać nimi
Istnieją dwa sposoby uruchamiania zadań opartych na wątkach:
Start-ThreadJob
— z modułu ThreadJobForEach-Object -Parallel -AsJob
— dodano funkcję równoległą w programie PowerShell 7.0
Użyj tych samych poleceń cmdlet zadań opisanych w about_Jobs do zarządzania zadaniami opartymi na wątkach.
Korzystanie z akcji Start-ThreadJob
Moduł ThreadJob został po raz pierwszy dostarczony z programem PowerShell 6. Można go również zainstalować z Galeria programu PowerShell dla Windows PowerShell 5.1.
Aby uruchomić zadanie wątku na komputerze lokalnym, użyj Start-ThreadJob
polecenia cmdlet z poleceniem lub skryptem ujętym w nawiasy klamrowe ({ }
).
Poniższy przykład uruchamia zadanie wątku, które uruchamia Get-Process
polecenie na komputerze lokalnym.
Start-ThreadJob -ScriptBlock { Get-Process }
Polecenie Start-ThreadJob
zwraca ThreadJob
obiekt reprezentujący uruchomione zadanie. Obiekt zadania zawiera przydatne informacje o zadaniu, w tym jego bieżący stan uruchomienia. Zbiera wyniki zadania w miarę generowania wyników.
Korzystanie z akcji ForEach-Object -Parallel -AsJob
Program PowerShell 7.0 dodał nowy parametr ustawiony do ForEach-Object
polecenia cmdlet. Nowe parametry umożliwiają uruchamianie bloków skryptu w równoległych wątkach jako zadań programu PowerShell.
Dane można przekazać potokowi do .ForEach-Object -Parallel
Dane są przekazywane do bloku skryptu, który jest uruchamiany równolegle. Parametr -AsJob
tworzy obiekty zadań dla każdego wątku równoległego.
Następujące polecenie uruchamia zadanie zawierające zadania podrzędne dla każdej wartości wejściowej przesyłanej potokiem do polecenia. Każde zadanie podrzędne uruchamia Write-Output
polecenie z potokową wartością wejściową jako argumentem.
1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob
Polecenie ForEach-Object -Parallel
zwraca PSTaskJob
obiekt, który zawiera podrzędne zadania dla każdej wartości wejściowej potoku. Obiekt zadania zawiera przydatne informacje o stanie uruchamiania zadań podrzędnych. Zbiera wyniki podrzędnych zadań podczas generowania wyników.
Jak poczekać na ukończenie zadania i pobrać wyniki zadania
Możesz użyć poleceń cmdlet zadań programu PowerShell, takich jak Wait-Job
i Receive-Job
, aby poczekać na ukończenie zadania, a następnie zwrócić wszystkie wyniki wygenerowane przez zadanie.
Następujące polecenie uruchamia zadanie wątku, które uruchamia Get-Process
polecenie, a następnie czeka na ukończenie polecenia, a na koniec zwraca wszystkie wyniki danych wygenerowane przez polecenie .
Start-ThreadJob -ScriptBlock { Get-Process } | Wait-Job | Receive-Job
Następujące polecenie uruchamia zadanie, które uruchamia Write-Output
polecenie dla każdego potokowego wejścia, a następnie czeka na ukończenie wszystkich zadań podrzędnych, a na koniec zwraca wszystkie wyniki danych wygenerowane przez zadania podrzędne.
1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob | Wait-Job | Receive-Job
Polecenie Receive-Job
cmdlet zwraca wyniki podrzędnych zadań.
1
3
2
4
5
Ponieważ każde zadanie podrzędne jest uruchamiane równolegle, kolejność wygenerowanych wyników nie jest gwarantowana.
Wydajność zadania wątku
Zadania wątków są szybsze i mniejsze niż inne typy zadań. Ale nadal mają narzut, który może być duży w porównaniu do pracy, którą wykonuje praca.
Program PowerShell uruchamia polecenia i skrypty w sesji. W sesji można uruchomić tylko jedno polecenie lub skrypt. Dlatego w przypadku uruchamiania wielu zadań każde zadanie jest uruchamiane w oddzielnej sesji. Każda sesja przyczynia się do narzutów.
Zadania wątków zapewniają najlepszą wydajność, gdy wykonywana praca jest większa niż obciążenie sesji użytej do uruchomienia zadania. Istnieją dwa przypadki, w których spełniają te kryteria.
Praca wymaga dużej mocy obliczeniowej — uruchamianie skryptu w wielu zadaniach wątków może korzystać z wielu rdzeni procesora i szybciej wykonywać zadania.
Praca składa się ze znaczącego oczekiwania — skrypt, który spędza czas na oczekiwaniu na wyniki we/wy lub wywołania zdalnego. Uruchamianie równoległe zwykle odbywa się szybciej niż w przypadku sekwencyjnego uruchamiania.
(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
W pierwszym przykładzie powyżej przedstawiono pętlę foreach, która tworzy 1000 zadań wątków w celu wykonania prostego zapisu ciągów. Ze względu na obciążenie zadania ukończenie trwa ponad 36 sekund.
Drugi przykład uruchamia polecenie cmdlet, ForEach
aby wykonać te same 1000 operacji.
Tym razem ForEach-Object
uruchamiane sekwencyjnie, w jednym wątku, bez żadnych obciążeń związanych z zadaniem. Kończy się w zaledwie 7 milisekundach.
W poniższym przykładzie zebrano maksymalnie 5000 wpisów dla 10 oddzielnych dzienników systemowych. Ponieważ skrypt obejmuje odczytywanie wielu dzienników, warto wykonać operacje równolegle.
$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
Skrypt kończy się w połowie czasu, gdy zadania są uruchamiane równolegle.
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
Zadania wątku i zmienne
Istnieje wiele sposobów przekazywania wartości do zadań opartych na wątkach.
Start-ThreadJob
Program może akceptować zmienne przesyłane potokiem do polecenia cmdlet, przekazywane do bloku skryptów za pośrednictwem słowa kluczowego lub przekazywane za pośrednictwem $using
parametru ArgumentList .
$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
akceptuje potok w zmiennych, a zmienne przekazywane bezpośrednio do bloku skryptu za pośrednictwem słowa kluczowego $using
.
$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
Ponieważ zadania wątku są uruchamiane w tym samym procesie, każdy typ odwołania do zmiennej przekazany do zadania musi być dokładnie traktowany. Jeśli nie jest to bezpieczny wątek obiekt, nigdy nie należy go przypisywać, a właściwości i metody nigdy nie powinny być wywoływane na nim.
Poniższy przykład przekazuje obiekt .NET ConcurrentDictionary
bezpieczny wątkowo do wszystkich zadań podrzędnych w celu zbierania unikatowych nazwanych obiektów procesu. Ponieważ jest to bezpieczny wątek obiekt, można go bezpiecznie używać, gdy zadania są uruchamiane współbieżnie w procesie.
$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