PowerShell 並行執行任務
在 PowerShell 中可以輕松的執行後臺任務並且讓多個後臺任務並行執行。本文介紹 PowerShell 中 Job 相關的一些命令,並通過 demo 演示如何在後臺同時執行多個任務。
PowerShell 中執行後臺任務的模式
下圖描述了在 PowerShell 中執行後臺任務的進程模型(此圖來自互聯網):
首先我們需要一個 PowerShell 進程執行與用戶交互的命令,比如執行 Start-Job 命令運行一個後臺任務。每一個這樣的後臺任務都會在一個新啟動的 PowerShell 進程中執行。所以,如果我們同時啟動三個後臺任務,那麽一共有四個 PowerShell 進程在同時運行。
Job 相關的命令
Start-Job 命令會啟動一個運行在後臺的任務。註意,每通過 Start-Job 命令運行一個任務都會創建一個單獨的 PowerShell 進程。
Stop-Job 命令用來停止一個正在運行的後臺任務(由 Start-Job 啟動的任務)。
Get-Job 命令用來獲得當前 session 中的後臺任務對象。
Wait-Job 命令阻塞當前的執行流程,等待指定的後臺任務執行結束。
Receive-Job 命令用來獲得後臺執行任務的執行結果。比如在一個後臺任務結束時,可以通過 Receive-Job 來得到結果,並輸出任務執行時的 output。
Remove-Job 命令刪除當前 session 中的已經完成的任務。當一個任務運行結束後,它並不會被自動刪除,除非你調用 Remove-Job 命令進行刪除,或者是關閉這個 session。如果使用 Remove-Job 刪除一個正在運行的任務,命令會運行失敗。此時需要先使用 Stop-Job 命令先停止任務,然後再用 Remove-Job 進行刪除。
在後臺執行任務
如果只是啟動一個後臺執行的任務,不需要知道任務執行的結果,也不關心任務何時執行結束,那麽僅僅使用 Start-Job 命令啟動任務的執行就可以了:
> Start-Job -ScriptBlock { sleep 5 }
啟動單個任務並等待任務結束
多數情況下我們是需要知道任務的結束時間的,此時可以通過 Wait-Job 命令阻塞執行流程,直到等待的任務結束:
> Start-Job -ScriptBlock { sleep 5; Write-Host "Hello world."; } | Wait-Job
註意上面的內容是由 Wait-Job 命令輸出的,當時任務的狀態為 "Completed"。
更進一步,我們還想要獲得任務執行過程中的輸出。這時我們就需要用到 Receive-Job 命令。你可以在任務啟動後的任何時刻執行 Receive-Job 命令,但是如果想要得到完整的輸出,就需要在任務結束後調用,此時需要配合 Wait-Job 命令一起使用:
$job = Start-Job -ScriptBlock { sleep 5; Write-Host "Hello world."; } Wait-Job $job Receive-Job -Job $job
把上面的代碼保存到文件 mytask.ps1 中執行:
Receive-Job 命令輸出了我們在後臺執行的任務的 output。
在後臺執行多個任務並等待結束
因為 Start-Job 命令是非阻塞的,所以理論上我們可以執行任意多次從而啟動很多的後臺任務。和等待單個任務相同,仍然可以使用 Wait-Job 命令來等待所有的任務結束,不過此時需要配合 Get-Job 命令一起使用:
> Get-Job | Wait-Job
更常用的方式是我們在 while 循環中不斷的檢查任務的狀態,當所有任務的狀態都是 "Completed" 時表示全部任務執行結束:
Remove-Job * #測試計時開始 $start_time = (Get-Date) Start-Job -ScriptBlock { sleep 9; Write-Host "Hello myJob1."; } -Name "myJob1" Start-Job -ScriptBlock { sleep 5; Write-Host "Hello myJob2."; } -Name "myJob2" $taskCount = 2 while($taskCount -gt 0) { foreach($job in Get-Job) { $state = [string]$job.State if($state -eq "Completed") { Write-Host($job.Name + " 已經完成") Receive-Job $job $taskCount-- Remove-Job $job } } sleep 1 } "所有任務已完成" #得出任務運行的時間 (New-TimeSpan $start_time).totalseconds
把上面的代碼保存到 mytask.ps1 文件中並執行:
代碼中我們給每個任務起了名字,並在 while 循環中不斷的使用 Get-Job 命令檢查任務當前的狀態,如果發現任務的狀態為 "Completed",就通過 Remove-Job 命令刪除它,並在刪除前打印任務的名稱和 output。
封裝一個執行後臺任務的函數
下面我們用封裝一個簡單的函數來並行執行多個任務:
function Run-Tasks { Param ( $taskArr, $parallelcount=1 ) #測試計時開始 $startTime = (Get-Date) #移除本次會話中已有的所有後臺任務 Remove-Job * # 使用變量 $taskCount 保存還沒有執行完成的任務數 $taskCount = $taskArr.Length #判斷設定的並行任務數是否超過當前任務隊列中的任務數 if($parallelCount -gt $taskArr.Length) { $parallelCount = $taskArr.Length } #啟動初始任務 foreach($i in 1..$parallelCount) { Start-Job $taskArr[$i - 1] -Name "task$i" } #初始任務完成後開始的任務 $nextIndex = $parallelCount #當任務隊列中還有任務時不斷輪詢已建立的任務,當一個後臺任務結束時刪除這個任務, #然後從任務隊列中取出下一個任務進行執行,然後等待所有任務執行完成。 while(($nextIndex -lt $taskArr.Length) -or ($taskCount -gt 0)) { foreach($job in Get-Job) { $state = [string]$job.State if($state -eq "Completed") { Write-Host($job.Name + " 已經完成,結果如下:") Receive-Job $job Remove-Job $job $taskCount-- if($nextIndex -lt $taskArr.Length) { $taskNumber = $nextIndex + 1 Start-Job $taskArr[$nextIndex] -Name "task$taskNumber" $nextIndex++ } } } sleep 1 } "所有任務已完成" #得出任務運行的時間 (New-TimeSpan $startTime).totalseconds }
上面的函數會在後臺執行用戶的任務,然後等待所有的任務執行結束。並且用戶可以指定同時執行的任務的個數,在任務執行完成後,輸出任務的 output。接下來讓我們嘗試使用這個函數執行一些任務:
#定義 6 個任務 $task1 = {sleep 12; Write-Host "Hello myJob1."; } $task2 = {sleep 5; Write-Host "Hello myJob2."; } $task3 = {sleep 8; Write-Host "Hello myJob3."; } $task4 = {sleep 3; Write-Host "Hello myJob4."; } $task5 = {sleep 20; Write-Host "Hello myJob5."; } $task6 = {sleep 15; Write-Host "Hello myJob6."; } #將 6 個任務寫入到一個數組中作為任務隊列 $taskArr = $task1, $task2, $task3, $task4, $task5, $task6 #運行數組中的任務,允許同時運行 4 個任務 Run-Tasks -taskArr $taskArr -parallelcount 4
下面是運行的結果:
總結
能夠隨心所欲的在後臺執行任務是一件感覺非常棒的事情!當然,對於工作來說你能夠把事情做得又快又好(又好可不敢說)。本文只是提供了一個簡單的運行並行任務的 demo,省略了異常處理等重要內容,但這已經足夠您開始 PowerShell 並行任務之旅了。
參考:
《Windows PowerShell 實戰第二版》
Powershell:簡單實現並行任務的腳本
PowerShell 並行執行任務