這個自我回答旨在為那些堅持使用 Windows PowerShell 并且由于公司政策等原因而無法安裝模塊的人提供一種簡單有效的并行替代方案。
在 Windows PowerShell 中,本地并行呼叫的內置可用替代方法是和,這兩種方法都非常慢、效率低,甚至不建議使用其中之一 ( ),并且在較新版本的 PowerShell中不再可用。Start-Jobworkflowworkflow
另一種選擇是依賴PowerShell SDK并使用System.Management.Automation.Runspaces命名空間提供的代碼撰寫我們自己的并行邏輯。這無疑是最有效的方法,并且是ForEach-Object -Parallel(在 PowerShell Core 中)以及Start-ThreadJob(預安裝在 PowerShell Core 中并通過PowerShell Gallery在 Windows PowerShell 中可用)在幕后使用的方法。
一個簡單的例子:
$throttlelimit = 3
$pool = [runspacefactory]::CreateRunspacePool(1, $throttlelimit)
$pool.Open()
$tasks = 0..10 | ForEach-Object {
$ps = [powershell]::Create().AddScript({
'hello world from {0}' -f [runspace]::DefaultRunspace.InstanceId
Start-Sleep 3
})
$ps.RunspacePool = $pool
@{ Instance = $ps; AsyncResult = $ps.BeginInvoke() }
}
$tasks | ForEach-Object {
$_.Instance.EndInvoke($_.AsyncResult)
}
$tasks.Instance, $pool | ForEach-Object Dispose
這很好,但是當代碼更復雜并因此帶來很多問題時,它會變得乏味并且經常變得復雜。
有更簡單的方法嗎?
uj5u.com熱心網友回復:
由于這是一個可能令人困惑的主題并且經常給站點帶來問題,因此我決定創建這個函式來簡化這項繁瑣的任務并幫助那些陷入 Windows PowerShell 的人。目的是讓它盡可能簡單和友好,它也應該是一個可以復制粘貼到我們的函式中,以便$PROFILE在需要時重復使用,而不需要安裝模塊(如問題中所述)。
此功能受到 RamblingCookieMonsterInvoke-Parallel和 Boe Prox 的極大啟發,PoshRSJob只是對那些進行了一些改進的簡化版。
筆記
此功能的進一步更新將發布到官方GitHub 存盤庫以及PowerShell 庫。此答案中的代碼將不再維護。
非常歡迎貢獻,如果您想貢獻,請分叉 repo 并提交包含更改的拉取請求。
定義
using namespace System.Collections.Generic
using namespace System.Management.Automation
using namespace System.Management.Automation.Runspaces
using namespace System.Management.Automation.Language
using namespace System.Text
function Invoke-Parallel {
[CmdletBinding(PositionalBinding = $false)]
[Alias('parallel', 'parallelpipeline')]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[object] $InputObject,
[Parameter(Mandatory, Position = 0)]
[scriptblock] $ScriptBlock,
[Parameter()]
[int] $ThrottleLimit = 5,
[Parameter()]
[hashtable] $Variables,
[Parameter()]
[ArgumentCompleter({
param(
[string] $commandName,
[string] $parameterName,
[string] $wordToComplete
)
(Get-Command -CommandType Filter, Function).Name -like "$wordToComplete*"
})]
[string[]] $Functions,
[Parameter()]
[ValidateSet('ReuseThread', 'UseNewThread')]
[PSThreadOptions] $ThreadOptions = [PSThreadOptions]::ReuseThread
)
begin {
try {
$iss = [initialsessionstate]::CreateDefault2()
foreach($key in $Variables.PSBase.Keys) {
$iss.Variables.Add([SessionStateVariableEntry]::new($key, $Variables[$key], ''))
}
foreach($function in $Functions) {
$def = (Get-Command $function).Definition
$iss.Commands.Add([SessionStateFunctionEntry]::new($function, $def))
}
$usingParams = @{}
foreach($usingstatement in $ScriptBlock.Ast.FindAll({ $args[0] -is [UsingExpressionAst] }, $true)) {
$varText = $usingstatement.Extent.Text
$varPath = $usingstatement.SubExpression.VariablePath.UserPath
# Credits to mklement0 for catching up a bug here. Thank you!
# https://github.com/mklement0
$key = [Convert]::ToBase64String([Encoding]::Unicode.GetBytes($varText))
if(-not $usingParams.ContainsKey($key)) {
$usingParams.Add($key, $ExecutionContext.SessionState.PSVariable.Get($varPath).Value)
}
}
$pool = [runspacefactory]::CreateRunspacePool(1, $ThrottleLimit, $iss, $Host)
$tasks = [List[hashtable]]::new()
$pool.ThreadOptions = $ThreadOptions
$pool.Open()
}
catch {
$PSCmdlet.ThrowTerminatingError($_)
}
}
process {
try {
# Thanks to Patrick Meinecke for his help here.
# https://github.com/SeeminglyScience/
$ps = [powershell]::Create().AddScript({
$args[0].InvokeWithContext($null, [psvariable]::new('_', $args[1]))
}).AddArgument($ScriptBlock.Ast.GetScriptBlock()).AddArgument($InputObject)
# This is how `Start-Job` does it's magic. Credits to Jordan Borean for his help here.
# https://github.com/jborean93
# Reference in the source code:
# https://github.com/PowerShell/PowerShell/blob/7dc4587014bfa22919c933607bf564f0ba53db2e/src/System.Management.Automation/engine/ParameterBinderController.cs#L647-L653
if($usingParams.Count) {
$null = $ps.AddParameters(@{ '--%' = $usingParams })
}
$ps.RunspacePool = $pool
$tasks.Add(@{
Instance = $ps
AsyncResult = $ps.BeginInvoke()
})
}
catch {
$PSCmdlet.WriteError($_)
}
}
end {
try {
foreach($task in $tasks) {
$task['Instance'].EndInvoke($task['AsyncResult'])
if($task['Instance'].HadErrors) {
$task['Instance'].Streams.Error
}
}
}
catch {
$PSCmdlet.WriteError($_)
}
finally {
$tasks.Instance, $pool | ForEach-Object Dispose
}
}
}
句法
Invoke-Parallel -InputObject <Object> [-ScriptBlock] <ScriptBlock> [-ThrottleLimit <Int32>]
[-ArgumentList <Hashtable>] [-ThreadOptions <PSThreadOptions>] [-Functions <String[]>] [<CommonParameters>]
要求
與Windows PowerShell 5.1和PowerShell Core 7 兼容。
安裝
如果您希望通過 Gallery 安裝它并將其作為模塊提供:
Install-Module PSParallelPipeline -Scope CurrentUser
例子
示例 1:并行批處理運行慢速腳本
$message = 'Hello world from {0}'
0..10 | Invoke-Parallel {
$using:message -f [runspace]::DefaultRunspace.InstanceId
Start-Sleep 3
} -ThrottleLimit 3
示例 2:與前面的示例相同,但帶有-Variables引數
$message = 'Hello world from {0}'
0..10 | Invoke-Parallel {
$message -f [runspace]::DefaultRunspace.InstanceId
Start-Sleep 3
} -Variables @{ message = $message } -ThrottleLimit 3
示例 3:添加到單執行緒安全實體
$sync = [hashtable]::Synchronized(@{})
Get-Process | Invoke-Parallel {
$sync = $using:sync
$sync[$_.Name] = @( $_ )
}
$sync
示例 4:與前面的示例相同,但-Variables用于將參考實體傳遞給運行空間
將參考實體傳遞給運行空間時推薦使用此方法,$using:在某些情況下可能會失敗。
$sync = [hashtable]::Synchronized(@{})
Get-Process | Invoke-Parallel {
$sync[$_.Name] = @( $_ )
} -Variables @{ sync = $sync }
$sync
示例 5:演示如何將本地定義的函式傳遞到運行空間范圍
function Greet { param($s) "$s hey there!" }
0..10 | Invoke-Parallel {
Greet $_
} -Functions Greet
引數
-輸入物件
指定要在 ScriptBlock 中處理的輸入物件。
注意:此引數旨在從管道系結。
Type: Object
Parameter Sets: (All)
Aliases:
Required: True
Position: Named
Default value: None
Accept pipeline input: True (ByValue)
Accept wildcard characters: False
-腳本塊
指定對每個輸入物件執行的操作。
此腳本塊針對管道中的每個物件運行。
Type: ScriptBlock
Parameter Sets: (All)
Aliases:
Required: True
Position: 1
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
-油門限制
指定并行呼叫的腳本塊的數量。
輸入物件被阻止,直到運行腳本塊計數低于 ThrottleLimit。
默認值為5。
Type: Int32
Parameter Sets: (All)
Aliases:
Required: False
Position: Named
Default value: 5
Accept pipeline input: False
Accept wildcard characters: False
-變數
指定在腳本塊(運行空間)中可用的變數哈希表。哈希表鍵成為腳本塊內的變數名稱。
Type: Hashtable
Parameter Sets: (All)
Aliases:
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
-功能
本地會話中的現有功能可在腳本塊(運行空間)中使用。
Type: String[]
Parameter Sets: (All)
Aliases:
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
-執行緒選項
這些選項控制在運行空間中執行命令時是否創建新執行緒。
此引數僅限于ReuseThread和UseNewThread。默認值為ReuseThread。
有關詳細資訊,請參閱PSThreadOptions列舉。
Type: PSThreadOptions
Parameter Sets: (All)
Aliases:
Accepted values: Default, UseNewThread, ReuseThread, UseCurrentThread
Required: False
Position: Named
Default value: ReuseThread
Accept pipeline input: False
Accept wildcard characters: False
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/525277.html
上一篇:在腳本中實作多執行緒/并行處理
下一篇:如何處理變數中的多個物件
