PowerShellでプロセス間通信で非同期処理を実現:名前付きパイプとRunspaceの活用

Tech

はじめに

PowerShellで、名前付きパイプとRunspaceを使用してプロセス間通信(IPC: Inter-Process Communication)を使って非同期処理を実現する方法について解説する。

スクリプトの概要

このスクリプトは、名前付きパイプを使用してメインプロセスと子プロセス間で通信を行うものである。メインプロセスはユーザーからのコマンドを受け取り、子プロセスに送信する。子プロセスはコマンドに応じた処理を行い、その結果をメインプロセスに返す。

必要なクラスのインポート

まず、並行処理のためのクラスを使用するために、System.Coreアセンブリをインポートする。

Add-Type -AssemblyName System.Core

同期ディクショナリの作成

次に、スレッド間で安全に共有できる同期ディクショナリを作成する。ConcurrentDictionaryは複数のスレッドから同時にアクセスしても安全である。

$syncHash = [System.Collections.Concurrent.ConcurrentDictionary[string,object]]::new()
$syncHash["Exit"] = $false
$syncHash["PipeStatus"] = "Initializing"

RunspacePoolの作成

RunspacePoolを使用することで、PowerShellコマンドを非同期で実行できる。ここでは、1つのスレッドのみを使用する設定にしている。

$runspacePool = [runspacefactory]::CreateRunspacePool(1, 1)
$runspacePool.Open()

子プロセス用のスクリプトブロック

このスクリプトブロックは別のRunspaceで実行され、名前付きパイプサーバーを作成してクライアントからの接続を待つ。

$childScriptBlock = {
    param($syncHash)

    $pipeName = "ControlPipe"
    try {
        $pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName, [System.IO.Pipes.PipeDirection]::InOut)
        $syncHash["PipeStatus"] = "Waiting for connection"
        $pipeServer.WaitForConnection()
        $syncHash["PipeStatus"] = "Connected"
        $reader = New-Object System.IO.StreamReader($pipeServer)
        $writer = New-Object System.IO.StreamWriter($pipeServer)
        $writer.AutoFlush = $true

        while (-not $syncHash["Exit"]) {
            if ($pipeServer.IsConnected -and $pipeServer.CanRead) {
                $command = $reader.ReadLine()
                if ($command -ne $null) {
                    switch ($command) {
                        "status" {
                            $writer.WriteLine("Child process is running")
                        }
                        "date" {
                            $writer.WriteLine((Get-Date).ToString("yyyy-MM-dd"))
                        }
                        "help" {
                            $helpMessage = "利用可能なコマンド:status - 子プロセスの状態を確認 date   - 現在の日付を表示exit   - プログラムを終了 help   - このヘルプメッセージを表示"
                            $writer.WriteLine($helpMessage)
                        }
                        "exit" {
                            $syncHash["Exit"] = $true
                            $writer.WriteLine("Exiting child process")
                        }
                        default {
                            $writer.WriteLine("Unknown command: $command")
                        }
                    }
                }
            }
            Start-Sleep -Milliseconds 100
        }
    }
    finally {
        if ($pipeServer -ne $null) {
            $pipeServer.Dispose()
        }
        $syncHash["PipeStatus"] = "Closed"
    }
}

子プロセスの開始

PowerShellオブジェクトを作成し、RunspacePoolを使用して非同期で実行する。

$childJob = [powershell]::Create().AddScript($childScriptBlock).AddArgument($syncHash)
$childJob.RunspacePool = $runspacePool
$childHandle = $childJob.BeginInvoke()

メインプロセスの処理

メインプロセスは名前付きパイプクライアントを作成して子プロセスに接続し、ユーザーからのコマンドを子プロセスに送信する。

try {
    Write-Host "子プロセスを起動しました。接続を待っています..."
    $pipeClient = New-Object System.IO.Pipes.NamedPipeClientStream(".", "ControlPipe", [System.IO.Pipes.PipeDirection]::InOut)
    $pipeClient.Connect(5000)  
    Write-Host "子プロセスに接続しました"
    $reader = New-Object System.IO.StreamReader($pipeClient)
    $writer = New-Object System.IO.StreamWriter($pipeClient)
    $writer.AutoFlush = $true

    while (-not $syncHash["Exit"]) {
        $command = Read-Host "コマンドを入力してください (status, date, help, exit)"
        if ($command -ne "") {
            $writer.WriteLine($command)
            Write-Host "送信: $command"
            $response = $reader.ReadLine()
            Write-Host "応答: $response"
            if ($command -eq "exit") {
                break
            }
        }
    }
}
catch {
    Write-Host "エラーが発生しました: $_"
}
finally {
    if ($pipeClient -ne $null) {
        $pipeClient.Close()
        $pipeClient.Dispose()
    }
    if ($childJob -ne $null) {
        if (-not $syncHash["Exit"]) {
            Write-Host "子プロセスがまだ実行中です。終了待ち..."
            $syncHash["Exit"] = $true
            Start-Sleep -Seconds 1
        }
        try {
            Write-Host "子プロセスを終了します..."
            $childJob.EndInvoke($childHandle)
            Write-Host "子プロセスが正常に終了しました。"
        }
        catch {
            Write-Host "子プロセスの終了中にエラーが発生しました: $_"
        }
        $childJob.Dispose()
    }
    if ($runspacePool -ne $null) {
        $runspacePool.Close()
        $runspacePool.Dispose()
    }
}

Write-Host "メインプロセスが終了しました。"

まとめ

このスクリプトは、PowerShellでプロセス間通信を実現するための基本的な方法を示している。名前付きパイプとRunspaceを活用することで、非同期処理+プロセス間で通信を行うことができる。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました