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 procesie bieżącej sesji.

Długi opis

Program PowerShell uruchamia jednocześnie polecenia i skrypty za pośrednictwem zadań. Program PowerShell zapewnia trzy typy zadań do obsługi współbieżności.

  • RemoteJob - Polecenia i skrypty są uruchamiane w sesji zdalnej. Aby uzyskać więcej informacji, 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 lub ThreadJob — 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 zadania w tle, ponieważ są uruchamiane w tym samym procesie w różnych wątkach. Jeśli jedno zadanie ma błąd krytyczny, który ulega awarii, wszystkie pozostałe zadania w procesie są przerywane.

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ą mniejszej ilości 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, gdy zadanie osiągnie stan zakończenia. Jeśli sesja nadrzędna zostanie przerwana, wszystkie uruchomione zadania podrzędne wraz z procesami podrzędnym zostaną zakończone.

Istnieją dwa sposoby na ominiecie tej sytuacji:

  1. Umożliwia Invoke-Command tworzenie zadań uruchamianych w sesjach rozłączonych. Aby uzyskać więcej informacji, zobacz about_Remote_Jobs.
  2. Użyj Start-Process funkcji , aby utworzyć nowy proces, a nie zadanie. Aby uzyskać więcej informacji, zobacz Uruchamianie procesu.

Jak uruchamiać zadania oparte na wątkach i zarządzać nimi

Istnieją dwa sposoby uruchamiania zadań opartych na wątku:

  • Start-ThreadJob— z modułu ThreadJob
  • ForEach-Object -Parallel -AsJob — funkcja równoległa została dodana w programie PowerShell 7.0

Do zarządzania zadaniami opartymi na wątkach użyj tych samych about_Jobs cmdlet zadań opisanych w te about_Jobs.

Korzystanie z akcji Start-ThreadJob

Moduł ThreadJob został najpierw 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, Start-ThreadJob użyj polecenia cmdlet z poleceniem lub skryptem ujętym w nawiasy klamrowe ({ }).

Poniższy przykład uruchamia zadanie wątków, które uruchamia polecenie Get-Process na komputerze lokalnym.

Start-ThreadJob -ScriptBlock { Get-Process }

Polecenie Start-ThreadJob zwraca obiekt ThreadJob reprezentujący uruchomione zadanie. Obiekt zadania zawiera przydatne informacje o zadaniu, w tym jego bieżący stan uruchomienia. Zbiera on wyniki zadania podczas generowania wyników.

Korzystanie z akcji ForEach-Object -Parallel -AsJob

W programie PowerShell 7.0 dodano nowy zestaw parametrów do polecenia ForEach-Object cmdlet . Nowe parametry umożliwiają uruchamianie bloków skryptów w równoległych wątkach jako zadania programu PowerShell.

Dane można potokować do .ForEach-Object -Parallel Dane są przekazywane do bloku skryptu, który jest uruchamiany równolegle. Parametr -AsJob tworzy obiekty zadań dla każdego z równoległych wątków.

Następujące polecenie uruchamia zadanie, które zawiera zadania podrzędne dla każdej wartości wejściowej potoku do polecenia. Każde zadanie podrzędne uruchamia polecenie Write-Output z wartością wejściową w potoku jako argumentem.

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

Polecenie ForEach-Object -Parallel zwraca obiekt zawierający PSTaskJob zadania podrzędne dla każdej wartości wejściowej potoku. Obiekt zadania zawiera przydatne informacje o uruchomionym stanie zadań podrzędnych. Zbiera on wyniki zadań podrzędne podczas ich generowania.

Jak poczekać na ukończenie zadania i pobrać wyniki zadania

Możesz użyć poleceń cmdlet zadań programu PowerShell, Wait-Job Receive-Job takich jak i , 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 polecenie, a Get-Process 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 polecenie dla każdego potoku danych wejściowych, a następnie czeka na ukończenie wszystkich zadań podrzędnych, a Write-Output 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ń. Jednak nadal mają one narzut, który może być duży w porównaniu do pracy, która jest w wykonaniu zadania.

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 obciążenia.

Zadania wątków zapewniają najlepszą wydajność, gdy ich praca jest większa niż obciążenie sesji używanej do uruchomienia zadania. Istnieją dwa przypadki, które spełniają te kryteria.

  • Praca wymaga intensywnych obliczeń — uruchomienie skryptu na wielu zadaniach wątków może korzystać z wielu rdzeni procesora i szybciej zakończyć pracę.

  • Praca składa się ze znacznego oczekiwania — skryptu, który poświęca czas na oczekiwanie na wyniki we/wy lub zdalnego wywołania. Uruchamianie równoległe zwykle kończy się szybciej niż w przypadku uruchamiania sekwencyjnego.

(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 pokazano pętlę foreach, która tworzy 1000 zadań wątków w celu prostego zapisu ciągów. Ze względu na obciążenie zadaniami ukończenie zadania trwa ponad 36 sekund.

Drugi przykład uruchamia polecenie ForEach cmdlet , aby wykonać te same 1000 operacji. Tym razem program jest ForEach-Object uruchamiany sekwencyjnie w jednym wątku bez narzutu na zadania. Jego ukończenie to zaledwie 7 milisekund.

W poniższym przykładzie dla 10 oddzielnych dzienników systemowych jest zbieranych maksymalnie 5000 wpisów. 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 jest uruchamiany 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ątków i zmienne

Istnieje wiele sposobów przekazania wartości do zadań opartych na wątku.

Start-ThreadJob może akceptować zmienne, które są przekazywane potokowo do polecenia cmdlet, przekazywane do bloku skryptu $using za pośrednictwem słowa kluczowego lub przekazywane za 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ątków są uruchamiane w tym samym procesie, każdy typ odwołania do zmiennej przekazany do zadania musi być starannie traktowany. Jeśli nie jest obiektem bezpiecznym wątkami, nigdy nie powinien być do niego przypisany, a metoda i właściwości nigdy nie powinny być na nim wywoływane.

Poniższy przykład przekazuje bezpieczny wątkowo obiekt .NET ConcurrentDictionary do wszystkich zadań podrzędnych w celu zbierania obiektów procesu o unikatowej nazwie. Ponieważ jest to obiekt bezpieczny wątkowo, można go bezpiecznie używać podczas współbieżnego uruchamiania zadań 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

Zobacz też