在 PowerShell 中要執行任務腳本,現在通常使用 Runspace,效率很高;任務比較多時,用 Runspace pool 來執行異步操作,可以控制資源池數量,就像 C# 中的線程池一樣
================================================
為了對比,我們分別采用同步和異步(多線程)方式,模擬執行10個任務,並且每個任務都接收一個參數,執行完成后返回執行結果
================================================
同步執行方法 輸入參數 $toexecute 是一個任務腳本數組,方法內遍歷任務腳本,直接通過腳本的 Invoke 方法執行(也可以創建一個 PowerShell 對象添加腳本,通過該 PowerShell 對象的 Invoke 方法執行),然后輸出執行結果
# 執行同步(單線程)任務 function RunJob { param($toexecute) # 遍歷執行所有腳本 [int]$arg = 0 foreach($s in $toexecute) { $result = $s.Invoke($arg++) # 執行帶參數的任務腳本 # 執行結果返回一個含有 Success 屬性的對象 if ($result.Success) { Write-Host (" -> 任務執行成功 " + $result.Data + ",當前線程 " + $result.ThreadId) -ForegroundColor Green } else { Write-Host (" -> 任務執行失敗 " + $result.Data + ",當前線程 " + $result.ThreadId) -ForegroundColor Red } } }
異步執行方法 輸入參數 $toexecute 是一個任務腳本數組,方法內遍歷任務腳本,
通過 PowerShell 對象 $psl 添加執行腳本和參數,返回一個作業對象 $job
通過 Runspace pool 對象 $rsp 控制異步多線程,
通過 $job 的 BeginInvoke 方法提交異步操作,
通過輪詢等待所有作業執行完成(IsCompleted),
通過 $job 的 EndInvoke 獲得執行結果
# 執行異步(多線程)任務 function RunJobAsync { param($toexecute) $rsp = [RunspaceFactory]::CreateRunspacePool(1, 5) #設置資源池中Runspace數量最少和最多 $rsp.Open() $jobs = @() [int]$arg = 0 # 遍歷執行所有腳本 foreach($s in $toexecute) { $psl = [Powershell]::Create() $job = $psl.AddScript($s).AddArgument($arg++) # 添加任務腳本和參數 $job.RunspacePool = $rsp Write-Host $("添加任務... " + $job.InstanceId) $jobs += New-Object PSObject -Property @{ Job = $job PowerShell = $psl Result = $job.BeginInvoke() # 異步執行任務腳本 } } # 輪詢等待任務完成 do { Start-Sleep -seconds 1 $cnt = ($jobs | Where {$_.Result.IsCompleted -ne $true}).Count Write-Host ("運行中的任務數量: " + $cnt) } while ($cnt -gt 0) foreach($r in $jobs) { Write-Host ("任務結果: " + $r.Job.InstanceId) $result = $r.Job.EndInvoke($r.Result) # 取得異步執行結果 # 注銷 PowerShell 對象 $r.PowerShell.Dispose() # 輸出完成的任務腳本 #Write-Output ($result) # 執行結果返回一個含有 Success 屬性的對象 if ($result.Success) { Write-Host (" -> 任務執行成功 " + $result.Data + ",當前線程 " + $result.ThreadId) -ForegroundColor Green } else { Write-Host (" -> 任務執行失敗 " + $result.Data + ",當前線程 " + $result.ThreadId) -ForegroundColor Red } } }
初始化任務腳本,循環創建10個任務腳本,每個任務通過等待1秒鍾模擬腳本執行,定義一個 PSObject 對象作為返回結果,其中屬性 Success 代表是否成功(故意設置傳入參數5時的任務失敗),Data 代表執行結果,ThreadId 標識當前線程ID
這種方式就像 C# 中的方法委托一樣(PowerShell 使用了大量.NET類庫),在調用端用委托定義執行過程和結果,然后將委托以變量形式傳遞給執行端
$toexecute = @() # 任務腳本列表 foreach($i in 1..10) { $toexecute += { param($state) #可接收參數 Start-Sleep -Seconds 1 New-Object PSObject -Property @{ Success = $state -ne 5 # 假設傳入參數5時失敗,其余成功 Data = "結果 $state" # 假設Data是執行結果,帶上傳入參數以區分 ThreadId = [AppDomain]::GetCurrentThreadId() # 當前線程ID } } }
注:PowerShell 對象 AddScript 加載腳本執行,也可以傳入一個腳本文件路徑,因此每個任務腳本可以寫到單獨的 .ps1 文件中
============================================================================
下面調用同步方法 RunJob,並且測量執行時間
Clear-Host $watch = Measure-Command { RunJob -toexecute $toexecute } $elapsed = [Math]::Round($watch.TotalMilliseconds / 1000.0, 2) Write-Output ("同步執行 "+ $toexecute.Count +" 個任務耗時" + $elapsed + "秒")
不出所料,在1個線程 20512 中執行10個任務,耗時10.06秒

============================================================================
下面調用異步方法 RunJobAsync,並且測量執行時間
Clear-Host $watch = Measure-Command { RunJobAsync -toexecute $toexecute } $elapsed = [Math]::Round($watch.TotalMilliseconds / 1000.0, 2) Write-Output ("異步執行 "+ $toexecute.Count +" 個任務耗時" + $elapsed + "秒")
執行結果如下圖,在5個線程中執行10個任務(差不多每個線程執行2個任務),耗時僅2.15秒
如果我們將代碼中 [RunspaceFactory]::CreateRunspacePool(1, 5) 中最大資源數改為10,基本每個任務都能有1個線程執行,測試耗時就1秒多一點點

============================================================================
寫得有點亂,就當是筆記了
參考資料
Multithreading with PowerShell using RunspacePool
