1. ホーム
  2. スクリプト・コラム
  3. パワーシェル

PowerShellのジョブ関連コマンドとタスクの並列実行の解説

2022-01-04 18:18:31

前置き

PowerShellでは、バックグラウンドタスクの実行や、複数のバックグラウンドタスクを並列に実行させることが簡単にできます。この記事では、PowerShellのジョブ関連コマンドを紹介し、バックグラウンドで複数のタスクを同時に実行する方法をデモを交えて紹介します。さっそく、詳しい紹介を見ていきましょう。

PowerShellでバックグラウンドタスクを実行するためのモード

PowerShellでバックグラウンドタスクを実行する際のプロセスモデルを以下の図に示します(この図はインターネットから引用しています)。

まず、Start-Job コマンドでバックグラウンドタスクを実行するなど、ユーザーと対話するコマンドを実行するための PowerShell プロセスが必要です。そのようなバックグラウンドタスクは、それぞれ新しく起動されたPowerShellプロセスで実行されます。つまり、3つのバックグラウンドタスクを同時に開始すると、4つのPowerShellプロセスが同時に実行されることになります。

ジョブ関連コマンド

Start-Jobコマンドは、バックグラウンドで実行されるタスクを開始します。Start-Jobコマンドによって実行される各タスクのために、個別のPowerShellプロセスが作成されることに注意してください。

Stop-Jobコマンドは、実行中のバックグラウンドタスク(Start-Jobによって開始されたタスク)を停止するために使用されます。

Get-Jobコマンドは、現在のセッションのバックグラウンド・タスク・オブジェクトを取得するために使用されます。

その

Wait-Jobコマンドは、現在の実行プロセスをブロックし、指定されたバックグラウンドタスクの実行が終了するのを待ちます。

その

Receive-Jobコマンドは、バックグラウンド実行タスクの実行結果を取得するために使用します。例えば、バックグラウンドタスクの終了時に、Receive-Jobを使ってタスク実行の結果を取得し、出力することができます。

は、その

Remove-Jobコマンドは、現在のセッションから完了したタスクを削除します。実行中のタスクを削除するためにRemove-Jobを使用した場合、コマンドは失敗します。この場合、最初にStop-Jobコマンドでタスクを停止してから、Remove-Jobを使用して削除する必要があります。

バックグラウンドでタスクを実行する

バックグラウンドで実行されるタスクを開始するだけで、タスクの実行結果を知る必要がなく、タスクの終了時刻も気にしない場合は、Start-Jobコマンドを使用してタスクの実行を開始すれば十分です。

> Start-Job -ScriptBlock { sleep 5 }

1つのタスクを開始し、終了を待つ

ほとんどの場合、タスクがいつ終了するかを知る必要があるので、待機中のタスクが終了するまで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コマンドは、バックグラウンドで実行しているタスクの出力を出力します。

バックグラウンドで複数のタスクを実行し、終了を待つ

Start-Jobコマンドはノンブロッキングなので、理論的には何度でも実行でき、好きなだけバックグラウンドタスクを開始することができます。1つのタスクを待つのと同様に、Wait-Jobコマンドを使用してすべてのタスクの終了を待つこともできますが、この場合、Get-Jobコマンドと組み合わせて使用する必要があります。

> Get-Job | Wait-Job

より一般的な方法は、whileループでタスクの状態をチェックし続け、すべてのタスクの状態が"Completed"になったら、すべてのタスクが実行を終了したことを意味します。

Remove-Job *
#Test timer start
$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 + " Completed")
   Receive-Job $job
   $taskCount--
   Remove-Job $job
  }
 }
 sleep 1
}
"All tasks have been completed" 
# derive the time the task ran
(New-TimeSpan $start_time).totalseconds

上記のコードをmytask.ps1ファイルに保存して実行します。

このコードでは、各タスクに名前を付け、whileループでGet-Jobコマンドを使用してタスクの現在の状態をチェックし続けています。もしタスクのステータスが "Completed"であることがわかれば、Remove-Jobコマンドでそれを取り除き、それを取り除く前にタスクの名前と出力を表示します。

バックグラウンドタスクを実行する関数をラッピングする

ここでは、複数のタスクを並行して実行するための簡単な関数をラップしています。

function Run-Tasks
{
 Param
 (
  $taskArr,
  $parallelcount=1
 )
 #Test timer starts
 $startTime = (Get-Date)
  #Remove all existing background tasks from this session
 Remove-Job *
 # Use the variable $taskCount to store the number of tasks that have not yet been executed
 $taskCount = $taskArr.Length
 
 # Determine if the set number of parallel tasks exceeds the number of tasks in the current task queue
 if($parallelCount -gt $taskArr.Length)
 {
  $parallelCount = $taskArr.Length
 }
 # Start the initial tasks
 foreach($i in 1... $parallelCount)
 {
  Start-Job $taskArr[$i - 1] -Name "task$i"
 }
 #Tasks that start after the initial task completes
 $nextIndex = $parallelCount
 #keep polling the created tasks while there are still tasks in the task queue, and delete a background task when it finishes
 #then take the next task from the task queue for execution, and wait for all tasks to finish executing.
 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 + " Completed with the following result:")
    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
 }
 "All tasks have been completed"
 # derive the time the task ran
 (New-TimeSpan $startTime).totalseconds
}

上記の関数は、ユーザーのタスクをバックグラウンドで実行し、すべてのタスクが終了するのを待ちます。次に、この関数を使って、いくつかのタスクを実行してみましょう。

#define 6 tasks
$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."; } 
# write 6 tasks to an array as a task queue
$taskArr = $task1, $task2, $task3, $task4, $task5, $task6
#Run the tasks in the array, allowing 4 tasks to run simultaneously
Run-Tasks -taskArr $taskArr -parallelcount 4

以下は実行結果です。

概要

バックグラウンドで好きなようにタスクを実行できるのは、とても気持ちのいいものです もちろん、仕事の場合は、速く、うまく(とは言えないけど)物事を終わらせることができます。この記事では、例外処理などの重要なことは省いて、並列タスクを実行する簡単なデモを提供するだけですが、PowerShell の並列タスクの旅を始めるには十分な内容になっています。

参考にしてください。

Windows PowerShellハンズオン 第2版
Powershell。並列タスクのためのシンプルなスクリプト