ja-JP/about_Thread_Jobs.help.txt

TOPIC
    about_Thread_Jobs

簡単な説明
    PowerShell のスレッドベースのジョブに関する情報を提供します。スレッド
    ジョブは、現在のセッションプロセス内の別のスレッドでコマンドまたは式を
    実行する、バックグラウンドジョブの一種です。

詳細な説明
    PowerShell は、ジョブを通じてコマンドとスクリプトを同時に実行します。
    PowerShell には、同時実行をサポートする 3 種類のジョブタイプがあります。

    - RemoteJob - コマンドとスクリプトはリモートセッションで実行されます。
      詳細については、about_Remote_Jobs を参照してください。
    - BackgroundJob - コマンドとスクリプトはローカルマシン上の別のプロセスで
      実行されます。詳細については、about_Jobs を参照してください。
    - PSTaskJob または ThreadJob - コマンドとスクリプトはローカルマシン上の
      同じプロセス内の別のスレッドで実行されます。

    スレッドベースのジョブは、同じプロセス内の異なるスレッドで実行されるため、
    リモートジョブやバックグラウンドジョブほど堅牢ではありません。1 つの
    ジョブにプロセスをクラッシュさせる致命的なエラーが発生すると、そのプロセス
    内の他のすべてのジョブが終了します。

    ただし、スレッドベースのジョブはオーバーヘッドが少なくて済みます。リモート
    処理層やシリアル化を使用しません。結果オブジェクトは、現在のセッション内の
    ライブオブジェクトへの参照として返されます。このオーバーヘッドがないため、
    スレッドベースのジョブは他のジョブタイプよりも高速に実行され、使用する
    リソースも少なくなります。

    重要: ジョブを作成した親セッションは、ジョブの状態を監視し、パイプライン
    データを収集します。ジョブの子プロセスは、ジョブが完了状態に達すると親
    プロセスによって終了されます。親セッションが終了すると、実行中のすべての
    子ジョブも、それらの子プロセスとともに終了します。

    この状況を回避する方法は 2 つあります。

    1. Invoke-Command を使用して、切断されたセッションで実行されるジョブを
       作成します。詳細については、about_Remote_Jobs を参照してください。
    2. ジョブではなく Start-Process を使用して新しいプロセスを作成します。
       詳細については、Start-Process を参照してください。

  スレッドベースのジョブを開始および管理する方法

    スレッドベースのジョブを開始するには 2 つの方法があります。

    - Start-ThreadJob - ThreadJob モジュールから
    - ForEach-Object -Parallel -AsJob - 並列機能は PowerShell 7.0 で
      追加されました

    スレッドベースのジョブを管理するには、about_Jobs で説明されているのと
    同じ Job コマンドレットを使用します。

    Start-ThreadJob の使用

    ThreadJob モジュールは PowerShell 6 で初めて提供されました。Windows
    PowerShell 5.1 用に PowerShell ギャラリーからインストールすることも
    できます。

    ローカルコンピューターでスレッドジョブを開始するには、波かっこ ({ }) で
    囲んだコマンドまたはスクリプトを指定して Start-ThreadJob コマンドレットを
    使用します。

    次の例は、ローカルコンピューターで Get-Process コマンドを実行する
    スレッドジョブを開始します。

        Start-ThreadJob -ScriptBlock { Get-Process }

    Start-ThreadJob コマンドは、実行中のジョブを表す ThreadJob オブジェクトを
    返します。ジョブオブジェクトには、現在の実行状態を含むジョブに関する有用な
    情報が含まれています。結果が生成されるにつれて、ジョブの結果を収集します。

    ForEach-Object -Parallel -AsJob の使用

    PowerShell 7.0 では、ForEach-Object コマンドレットに新しいパラメーター
    セットが追加されました。新しいパラメーターを使用すると、スクリプトブロックを
    PowerShell ジョブとして並列スレッドで実行できます。

    ForEach-Object -Parallel にデータをパイプできます。データは、並列で実行
    されるスクリプトブロックに渡されます。-AsJob パラメーターは、並列スレッドの
    それぞれに対してジョブオブジェクトを作成します。

    次のコマンドは、コマンドにパイプされた各入力値に対する子ジョブを含むジョブを
    開始します。各子ジョブは、パイプされた入力値を引数として Write-Output
    コマンドを実行します。

        1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob

    ForEach-Object -Parallel コマンドは、パイプされた各入力値に対する子ジョブを
    含む PSTaskJob オブジェクトを返します。ジョブオブジェクトには、子ジョブの
    実行状態に関する有用な情報が含まれています。結果が生成されるにつれて、
    子ジョブの結果を収集します。

  ジョブの完了を待機してジョブ結果を取得する方法

    Wait-Job や Receive-Job などの PowerShell のジョブコマンドレットを使用
    して、ジョブの完了を待機し、ジョブによって生成されたすべての結果を返す
    ことができます。

    次のコマンドは、Get-Process コマンドを実行するスレッドジョブを開始し、
    コマンドの完了を待機して、最後にコマンドによって生成されたすべてのデータ
    結果を返します。

        Start-ThreadJob -ScriptBlock { Get-Process } | Wait-Job | Receive-Job

    次のコマンドは、パイプされた各入力に対して Write-Output コマンドを実行する
    ジョブを開始し、すべての子ジョブの完了を待機して、最後に子ジョブによって
    生成されたすべてのデータ結果を返します。

        1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob | Wait-Job | Receive-Job

    Receive-Job コマンドレットは、子ジョブの結果を返します。

        1
        3
        2
        4
        5

    各子ジョブは並列で実行されるため、生成される結果の順序は保証されません。

  スレッドジョブのパフォーマンス

    スレッドジョブは、他の種類のジョブよりも高速で軽量です。ただし、ジョブが
    行う作業と比較して大きくなる可能性のあるオーバーヘッドが依然として
    あります。

    PowerShell は、セッション内でコマンドとスクリプトを実行します。セッション
    内では、一度に 1 つのコマンドまたはスクリプトしか実行できません。そのため、
    複数のジョブを実行する場合、各ジョブは別々のセッションで実行されます。
    各セッションがオーバーヘッドに寄与します。

    スレッドジョブは、実行する作業がジョブの実行に使用されるセッションの
    オーバーヘッドより大きい場合に、最高のパフォーマンスを提供します。この
    基準を満たすケースは 2 つあります。

    - 作業が計算量の多いもの - 複数のスレッドジョブでスクリプトを実行すると、
      複数のプロセッサコアを活用して、より速く完了できます。

    - 作業が大幅な待機を伴うもの - I/O やリモート呼び出しの結果を待機する時間を
      費やすスクリプト。並列で実行すると、通常は順次実行するよりも速く完了
      します。

        (Measure-Command {
            1..1000 | foreach { Start-ThreadJob { Write-Output "Hello $Using:_" } } | Receive-Job -Wait
        }).TotalMilliseconds
        36860.8226

        (Measure-Command {
            1..1000 | ForEach-Object { "Hello: $_" }
        }).TotalMilliseconds
        7.1975

    上記の最初の例は、単純な文字列の書き込みを行うために 1000 個のスレッド
    ジョブを作成する foreach ループを示しています。ジョブのオーバーヘッドの
    ため、完了までに 36 秒以上かかります。

    2 番目の例は、同じ 1000 回の操作を行うために ForEach-Object コマンドレットを
    実行します。今回、ForEach-Object はジョブのオーバーヘッドなしで、単一の
    スレッドで順次実行されます。わずか 7 ミリ秒で完了します。

    次の例では、10 個の個別のシステムログに対して最大 5000 エントリが収集
    されます。スクリプトは多数のログの読み取りを伴うため、操作を並列で行うのが
    理にかなっています。

        $logNames.Count
        10

        Measure-Command {
            $logs = $logNames | ForEach-Object {
                Get-WinEvent -LogName $_ -MaxEvents 5000 2>$null
            }
        }

        TotalMilliseconds : 252398.4321 (4 minutes 12 seconds)
        $logs.Count
        50000

    ジョブを並列で実行すると、スクリプトは半分の時間で完了します。

        Measure-Command {
            $logs = $logNames | foreach {
                Start-ThreadJob {
                    Get-WinEvent -LogName $Using:_ -MaxEvents 5000 2>$null
                } -ThrottleLimit 10
            } | Wait-Job | Receive-Job
        }

        TotalMilliseconds : 115994.3 (1 minute 56 seconds)
        $logs.Count
        50000

  スレッドジョブと変数

    スレッドベースのジョブに値を渡す方法は複数あります。

    Start-ThreadJob は、コマンドレットにパイプされた変数、Using: スコープ
    修飾子を介してスクリプトブロックに渡された変数、または ArgumentList
    パラメーターを介して渡された変数を受け入れることができます。

        $msg = "Hello"

        $msg | Start-ThreadJob { $input | Write-Output } | Wait-Job | Receive-Job

        Start-ThreadJob { Write-Output $Using:msg } | Wait-Job | Receive-Job

        Start-ThreadJob { param ([string] $Message) Write-Output $Message } -ArgumentList @($msg) |
          Wait-Job | Receive-Job

    ForEach-Object -Parallel は、パイプされた変数と、Using: スコープ修飾子を
    介してスクリプトブロックに直接渡された変数を受け入れます。

        $msg = "Hello"

        $msg | ForEach-Object -Parallel { Write-Output $_ } -AsJob | Wait-Job | Receive-Job

        1..1 | ForEach-Object -Parallel { Write-Output $Using:msg } -AsJob | Wait-Job | Receive-Job

    スレッドジョブは同じプロセス内で実行されるため、ジョブに渡される参照型の
    変数は慎重に扱う必要があります。スレッドセーフなオブジェクトでない場合は、
    決して代入してはならず、メソッドやプロパティを呼び出してはなりません。

    次の例は、一意の名前を持つプロセスオブジェクトを収集するために、スレッド
    セーフな .NET ConcurrentDictionary オブジェクトをすべての子ジョブに渡し
    ます。これはスレッドセーフなオブジェクトであるため、ジョブがプロセス内で
    同時に実行されている間も安全に使用できます。

        $threadSafeDictionary = [System.Collections.Concurrent.ConcurrentDictionary[string,object]]::new()
        $jobs = Get-Process | foreach {
            Start-ThreadJob {
                $proc = $Using:_
                $dict = $Using:threadSafeDictionary
                $dict.TryAdd($proc.ProcessName, $proc)
            }
        }
        $jobs | Wait-Job | Receive-Job

        $threadSafeDictionary.Count
        96

        $threadSafeDictionary["pwsh"]

        NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
        ------ ----- ----- ------ -- -- -----------
          112 108.25 124.43 69.75 16272 1 pwsh

関連項目
    about_Job_Details
    about_Remote_Jobs
    about_Thread_Jobs
    about_PSSessions
    about_Remote
    Get-Job
    Receive-Job
    Remove-Job
    Start-Job
    Stop-Job
    Wait-Job

----
原文: PowerShell-Docs (CC BY 4.0) の翻訳 / PSHelpJaJP