about_Thread_Jobs

簡短描述

提供PowerShell線程型作業的相關信息。 線程作業是背景作業的類型,可在目前會話進程中的個別線程中執行命令或表達式。

詳細描述

PowerShell 會透過作業同時執行命令和腳本。 PowerShell 提供的作業類型有三種,可支援並行。

  • RemoteJob - 命令和文稿會在遠程會話中執行。 如需詳細資訊,請參閱 about_Remote_Jobs
  • BackgroundJob - 命令和文稿會在本機計算機上的個別進程中執行。 如需詳細資訊,請參閱 about_Jobs
  • PSTaskJobThreadJob - 命令和文稿會在本機計算機上相同進程的個別線程中執行。

線程型作業不像遠端和背景工作那麼強固,因為它們在不同的線程上在同一個進程中執行。 如果某個作業發生嚴重錯誤而損毀進程,則進程中的所有其他作業都會終止。

不過,線程型作業需要較少的額外負荷。 它們不會使用遠端層或串行化。 結果物件會以目前會話中即時對象的參考傳回。 若沒有此額外負荷,線程型作業的執行速度會更快,且使用的資源比其他作業類型少。

重要

建立作業的父會話也會監視作業狀態並收集管線數據。 作業子進程會在作業達到完成狀態后,由父進程終止。 如果父會話終止,則所有執行中的子作業都會連同其子進程一起終止。

有兩種方式可解決這種情況:

  1. 用來 Invoke-Command 建立在中斷聯機會話中執行的作業。 如需詳細資訊,請參閱 about_Remote_Jobs
  2. 使用 Start-Process 來建立新的進程,而不是作業。 如需詳細資訊,請參閱 Start-Process

如何啟動和管理線程型作業

有兩種方式可以啟動線程型作業:

  • Start-ThreadJob - 從 ThreadJob 模組
  • ForEach-Object -Parallel -AsJob - 已在 PowerShell 7.0 中新增平行功能

使用about_Jobs中所述的相同作業 Cmdlet 來管理線程型作業。

使用 Start-ThreadJob

ThreadJob 模組會先隨附於 PowerShell 6。 您也可以從適用於 Windows PowerShell 5.1 的 PowerShell 資源庫 安裝。

若要在本機計算機上啟動線程作業,請使用 Start-ThreadJob Cmdlet 搭配以大括弧 ({ }) 括住的命令或腳本。

下列範例會啟動在本機計算機上執行 Get-Process 命令的線程作業。

Start-ThreadJob -ScriptBlock { Get-Process }

此命令 Start-ThreadJob 會傳 ThreadJob 回物件,代表執行中作業。 作業物件包含作業的實用資訊,包括其目前執行狀態。 它會收集產生結果時的工作結果。

使用 ForEach-Object -Parallel -AsJob

PowerShell 7.0 已將新的參數設定為 ForEach-Object Cmdlet。 新的參數可讓您在PowerShell作業的平行線程中執行腳本區塊。

您可以使用管線將資料傳送至 ForEach-Object -Parallel。 數據會傳遞至平行執行的腳本區塊。 參數 -AsJob 會為每個平行線程建立作業物件。

下列命令會啟動作業,其中包含傳送至命令之每個輸入值的子作業。 每個子作業都會 Write-Output 以管線輸入值作為自變數來執行命令。

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

此命令 ForEach-Object -ParallelPSTaskJob 傳回 物件,其中包含每個管道輸入值的子工作。 作業物件包含子作業執行狀態的實用資訊。 它會收集產生結果時子作業的結果。

如何等候作業完成並擷取作業結果

您可以使用PowerShell作業 Cmdlet,例如 Wait-JobReceive-Job 來等候作業完成,然後傳回作業所產生的所有結果。

下列命令會啟動執行命令的線程作業 Get-Process ,然後等候命令完成,最後傳回命令所產生的所有數據結果。

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

下列命令會啟動針對每個管道輸入執行 Write-Output 命令的作業,然後等候所有子作業完成,最後傳回子作業所產生的所有數據結果。

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

Cmdlet 會 Receive-Job 傳回子作業的結果。

1
3
2
4
5

由於每個子作業都平行執行,因此無法保證產生的結果順序。

線程作業效能

線程作業比其他類型的作業更快且較輕。 但是,相較於工作所執行的工作,它們仍有可能很大的額外負荷。

PowerShell 會在會話中執行命令和腳本。 會話中一次只能執行一個命令或腳本。 因此,執行多個作業時,每個作業都會在不同的會話中執行。 每個會話都會造成額外負荷。

當線程作業執行的工作大於用來執行作業之會話的額外負荷時,線程作業可提供最佳效能。 有兩種情況符合此準則。

  • 工作需要大量運算 - 在多個線程作業上執行腳本,可以利用多個處理器核心並更快完成。

  • 工作包含大量等候 - 腳本,花費時間等候 I/O 或遠端呼叫結果。 以平行方式執行通常會比循序執行更快。

(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

上述第一個範例顯示 foreach 循環,可建立 1000 個線程作業來執行簡單的字串寫入。 由於作業額外負荷,需要超過 36 秒才能完成。

第二個範例會 ForEach 執行 Cmdlet 來執行相同的 1000 個作業。 這次, ForEach-Object 會循序在單個線程上執行,而不需要任何作業額外負荷。 它只會以 7 毫秒完成。

在下列範例中,最多會針對10個不同的系統記錄收集5000個專案。 由於腳本牽涉到讀取許多記錄,因此平行執行作業是合理的。

$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

腳本會在作業平行執行時完成一半的時間。

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

線程作業和變數

有多種方式可將值傳遞至線程型作業。

Start-ThreadJob 可以接受管線傳送至 Cmdlet 的變數、透過 $using 關鍵詞傳入腳本區塊,或透過 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 接受管線傳入變數,以及透過 關鍵詞直接傳遞至腳本區塊的 $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

由於線程作業會在相同的進程中執行,因此傳遞至作業的任何變數參考類型都必須謹慎處理。 如果它不是線程安全物件,則絕不應指派它,而且不應該在它上叫用方法和屬性。

下列範例會將安全線程的 .NET ConcurrentDictionary 對象傳遞給所有子作業,以收集唯一命名的進程物件。 因為它是線程安全物件,所以可以在作業在進程中同時執行時安全地使用它。

$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

另請參閱