PowerShell は 7.0 以降で、Foreach-Object で Parallel パラメーターが使用できるようになりました。
従来までは、複数スレッドで処理をする場合は、Windows PowerShell ワークフロー / ジョブ / Runspace などを使用するケースがありました。(Windows PowerShell ワークフローは 5.1 までしか使用できないので、現時点で利用することはほとんどない気もしますが)
- Windows PowerShell ワークフローの概念
- PowerShell ジョブを使用したコマンドレットの並列実行
- Beginning Use of PowerShell Runspaces: Part 1
PowerShell 7.0 以降では、Foreach-Object の Parallel パラメーターを使用することで複数スレッドの処理をシンプルに記述することができるようになっています。
$SampleText = "PowerShell Foreach-Object Parallel" Clear-Host Measure-Command { 1..50 | Foreach-Object { $text = $SampleText Write-Host ("{0} : {1}" -f $text, $_) } } Measure-Command { 1..50 | Foreach-Object -ThrottleLimit 5 -Parallel { $text = $using:SampleText Write-Host ("{0} : {1}" -f $text, $_) } }
PowerShell ForEach-Object Parallel Feature には次のように記載されています。
However, there is still quite a bit of overhead to run script blocks in parallel. Script blocks run in a context called a PowerShell
runspace
.
シンプルな処理では、スクリプトブロックを並行で実行する際のオーバーヘッドにより、Parallel を使用しない方が処理が短時間で実行されるケースもありますので、どのような処理を並行で実行するかについては注意しておく必要がありますが、1 回の実行に数秒かかる処理などは並行実行すると効果があるのではないでしょうか。
Azure Functions の PowerShell ランタイムも PowerShell のバージョンが 7.0 になっていますので、Azure 上で関数を実行する場合にも効果的に動作するケースがあるかと思います。(Azure Automation については How can we improve Azure Automation service? に記載されていますが、CY21 Q4 に、PowerSHell 7.0 をサポート予定のようです)
Foreach-Object で Parallel パラメーターを使用したした際の情報ですが、冒頭に記載したリリース時の情報だけでなく、docs のドキュメントでいくつかの情報が公開されていますので、どのような情報があるかをまとめておきたいと思います。
Foreach-Object のParallel についてのドキュメント
基本情報
PowerShell についてのドキュメントですが、https://docs.microsoft.com/ja-jp/powershell/scripting/learn/ps101/00-introduction?view=powershell-7.1 をはじめとして、「PoweShell の取得」のドキュメントツリーに様々な情報が公開されています。
この中に次のドキュメントがあり、Parallel パラメーターを使用した Foreach-Object についての解説が行われています。
「using:」ステートメントを使用して、親スコープの変数を使用する方法や、Foreach-Object Parallel を「-AsJob」を使用してジョブとして実行し、並行実行しているスクリプトブロックの進行状況を取得する方法が記載されています。
他にも ForEach-Object の解説にも Parallel パラメーターの情報が記載されていますので、基本的な利用方法については、この情報からも確認することができます。
エラーハンドリング
Parallel パラメーターのスクリプトブロックで発生した例外のエラーハンドリングが悩ましいところです。
Clear-Host try{ 1..10 | Foreach-Object -ThrottleLimit 5 -Parallel { Write-Host ("{0}" -f $_) if($_ % 2 -eq 0){ throw "Exception" } } }catch{ Write-Host "Catch Exception" }
このようなスクリプトを実行しても親スコープでは Exception を拾うことができません。
エラーハンドリングについては、次のような情報が公開されています。
- Error Handling within Foreach-object Parallel – Powershell 7
- Error handling within Foreach-object Parallel block – Powershell 7
シンプルなのは、ErrorValiable を使用する方法なのでしょうかね。
Clear-Host 1..10 | Foreach-Object -ThrottleLimit 5 -Parallel { Write-Host ("{0}" -f $_) if($_ % 2 -eq 0){ throw ("{0} Exception" -f $_) } } -ErrorVariable errorInfo foreach ($e in $errorInfo){ Write-Host $e }
ErrorVariable に指定した変数にスクリプトブロック内で発生した例外が「System.Management.Automation.ErrorRecord」として格納されているので、この情報から親スコープでエラーハンドリングを実施することができます。
$using を使用して親スコープの変数を書き換える方法になるので、使おうか悩ましいのですが、次のような方法でもエラーハンドリングはできるようです。
Clear-Host $l = New-Object System.Collections.ArrayList 1..10 | Foreach-Object -ThrottleLimit 5 -Parallel { Write-Host ("{0}" -f $_) try{ if($_ % 2 -eq 0){ throw ("{0} Exception" -f $_) } }catch{ $info = $using:l $info.Add($Error[0]) } } -ErrorVariable errorInfo foreach ($e in $l){ Write-Host $e }
まとめ
私はメインで使用しているスクリプトが PowerShell なので、Foreach-Object で Parallel パラメーターが使用できるようになって、簡単に複数のスレッドの処理が記載できるようになったのがとても助かっています。
PowerShell 7.0 より前は、DB に対して複数のクエリを平行に実行したい場合に、Runspace をどうやって作るんだっけというところから思い出していたのですが、7.0 以降は Foreach-Object でシンプルに処理が記載できるので助かっています。(Foreach-Object の Parallel パラメーターも Runspace を使用しているようですが、作成部分がラップされているので、サクッと書けて楽です)
Parallel パラメーターのスクリプトブロックで、親スコープで定義した関数を呼び出すのが私の理解度では難しく、ロジックの再利用性の高いスクリプトをどうやって作るかが悩ましいのですが、Foreach-Object の Parallel パラメーターが活躍できるケースが今後出てくるのではないでしょうか。
[…] https://blog.engineer-memo.com/2021/04/20/powershell-%e3%81%ae-foreach-object-%e3%81%ae-parallel-%e3… […]
【後で読みたい!】PowerShell の Foreach-Object の Parallel パラメーターの情報 | Tak's Bar
20 4月 21 at 22:54