about_Thread_Jobs
簡短描述
提供PowerShell線程型作業的相關信息。 線程作業是背景作業的類型,可在目前會話進程中的個別線程中執行命令或表達式。
詳細描述
PowerShell 會透過作業同時執行命令和腳本。 PowerShell 提供的作業類型有三種,可支援並行。
RemoteJob
- 命令和文稿會在遠程會話中執行。 如需詳細資訊,請參閱 about_Remote_Jobs。BackgroundJob
- 命令和文稿會在本機計算機上的個別進程中執行。 如需詳細資訊,請參閱 about_Jobs。PSTaskJob
或ThreadJob
- 命令和文稿會在本機計算機上相同進程的個別線程中執行。
線程型作業不像遠端和背景工作那麼強固,因為它們在不同的線程上在同一個進程中執行。 如果某個作業發生嚴重錯誤而損毀進程,則進程中的所有其他作業都會終止。
不過,線程型作業需要較少的額外負荷。 它們不會使用遠端層或串行化。 結果物件會以目前會話中即時對象的參考傳回。 若沒有此額外負荷,線程型作業的執行速度會更快,且使用的資源比其他作業類型少。
重要
建立作業的父會話也會監視作業狀態並收集管線數據。 作業子進程會在作業達到完成狀態后,由父進程終止。 如果父會話終止,則所有執行中的子作業都會連同其子進程一起終止。
有兩種方式可解決這種情況:
- 用來
Invoke-Command
建立在中斷聯機會話中執行的作業。 如需詳細資訊,請參閱 about_Remote_Jobs。 - 使用
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 -Parallel
會 PSTaskJob
傳回 物件,其中包含每個管道輸入值的子工作。 作業物件包含子作業執行狀態的實用資訊。 它會收集產生結果時子作業的結果。
如何等候作業完成並擷取作業結果
您可以使用PowerShell作業 Cmdlet,例如 Wait-Job
和 Receive-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