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 współbieżnie uruchamia polecenia i skrypty za pośrednictwem zadań. Istnieją trzy typy zadań udostępniane przez program PowerShell 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 sieciowej 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ą mniejszej liczby 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 procesami podrzędnymi.
Istnieją dwa sposoby obejścia tej sytuacji:
- Służy
Invoke-Command
do tworzenia zadań uruchamianych w sesjach bez połączenia. 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
— funkcja równoległa została dodana w programie PowerShell 7.0
Użyj tych samych poleceń cmdlet zadań opisanych w about_Jobs , aby zarządzać 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 programu 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 na ForEach-Object
polecenie cmdlet. Nowe parametry umożliwiają uruchamianie bloków skryptów w wątkach równoległych jako zadań programu PowerShell.
Możesz przekazać dane potokowe 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, które zawiera zadania podrzędne dla każdej wartości wejściowej przesyłane 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 zawierający zadania podrzędne dla każdej wartości wejściowej potoku. Obiekt zadania zawiera przydatne informacje o stanie uruchamiania zadań podrzędnych. Zbiera wyniki zadań podrzędnych w miarę 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
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 danych wejściowych, 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 zadań podrzędnych.
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 lżejsze niż inne typy zadań. Ale nadal mają nakłady pracy, które mogą być duże w porównaniu z pracą, którą wykonuje praca.
Program PowerShell uruchamia polecenia i skrypty w sesji. W sesji można uruchomić tylko jedno polecenie lub skrypt. Dlatego podczas 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 praca, którą wykonują, jest większa niż obciążenie sesji użytej do uruchomienia zadania. Istnieją dwa przypadki, które spełniają te kryteria.
Praca jest intensywnie obciążana obliczeniami — uruchamianie skryptu w wielu zadaniach wątków może korzystać z wielu rdzeni procesora i wykonywać je szybciej.
Praca składa się z znaczącego oczekiwania — skrypt, który spędza czas na oczekiwaniu na wyniki we/wy lub wywołania zdalnego. Uruchamianie równoległe zwykle przebiega szybciej niż sekwencyjnie.
(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ątku w celu wykonania prostego zapisu ciągów. Ze względu na obciążenie zadania ukończenie zajmuje 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 do 5000 wpisów jest zbieranych 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
może akceptować zmienne przesyłane potokiem do polecenia cmdlet, przekazywane do bloku skryptu za pośrednictwem słowa kluczowego lub przekazywane za $using
pośrednictwem 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 zmiennej przekazany do zadania musi być dokładnie traktowany. Jeśli nie jest to obiekt bezpieczny wątku, nigdy nie należy go przypisywać, a metody i właściwości 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 procesów. 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