PowerShell 7.0부터 Foreach-Object cmdlet의 병렬 매개 변수를 사용하여 동시에 여러 스레드에서 작업할 수 있습니다. 그렇지만 스레드의 진행률을 모니터링하는 것이 문제일 수 있습니다. 일반적으로 Write-Progress를 사용하여 프로세스의 진행률을 모니터링할 수 있습니다.
그러나 Parallel을 사용하면 PowerShell에서 각 스레드에 별도의 runspace를 사용하기 때문에 진행률을 다시 호스트에 보고하는 것이 일반적인 Write-Progress 사용만큼 간단하지 않습니다.
동기화된 해시 테이블을 사용하여 진행률 추적
여러 스레드에서 진행률을 작성할 때는 PowerShell에서 병렬 프로세스를 실행할 때 각 프로세스에 고유한 Runspace가 있기 때문에 추적이 어려워집니다. 이를 해결하려면 동기화된 해시 테이블을 사용할 수 있습니다. 동기화된 해시 테이블은 오류를 throw하지 않고 동시에 여러 스레드에서 수정할 수 있는 스레드로부터 안전한 데이터 구조입니다.
설정
이 방법의 단점 중 하나는 모든 것이 오류 없이 실행되도록 다소 복잡한 설정이 필요하기 때문에 발생합니다.
$dataset = @(
@{
Id = 1
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 2
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 3
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 4
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 5
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
)
# Create a hashtable for process.
# Keys should be ID's of the processes
$origin = @{}
$dataset | Foreach-Object {$origin.($_.id) = @{}}
# Create synced hashtable
$sync = [System.Collections.Hashtable]::Synchronized($origin)
이 섹션에서는 세 가지 용도로 세 가지 데이터 구조를 만듭니다.
변수는 $dataSet 수정될 위험 없이 다음 단계를 조정하는 데 사용되는 해시 테이블 배열을 저장합니다. 컬렉션을 반복하는 동안 개체 컬렉션이 수정되면 PowerShell에서 오류가 발생합니다. 루프의 개체 컬렉션을 수정할 개체와 별도로 유지해야 합니다. 각 해시 테이블의 Id 키는 모의 프로세스의 식별자입니다. 키는 Wait 추적 중인 각 모의 프로세스의 워크로드를 시뮬레이션합니다.
변수는 $origin 각 키가 모의 프로세스 ID 중 하나인 중첩된 해시 테이블을 저장합니다.
그런 다음, 변수에 저장된 동기화된 해시 테이블을 하이드레이션하는 $sync 데 사용됩니다. $sync 변수는 진행률을 표시하는 부모 runspace에 진행률을 다시 보고하는 작업을 수행합니다.
프로세스 실행
이 섹션에서는 다중 스레드 프로세스를 실행하고 진행률을 표시하는 데 사용되는 일부 출력을 만듭니다.
$job = $dataset | Foreach-Object -ThrottleLimit 3 -AsJob -Parallel {
$syncCopy = $using:sync
$process = $syncCopy.$($PSItem.Id)
$process.Id = $PSItem.Id
$process.Activity = "Id $($PSItem.Id) starting"
$process.Status = "Processing"
# Fake workload start up that takes x amount of time to complete
start-sleep -Milliseconds ($PSItem.wait*5)
# Process. update activity
$process.Activity = "Id $($PSItem.id) processing"
foreach ($percent in 1..100)
{
# Update process on status
$process.Status = "Handling $percent/100"
$process.PercentComplete = (($percent / 100) * 100)
# Fake workload that takes x amount of time to complete
Start-Sleep -Milliseconds $PSItem.Wait
}
# Mark process as completed
$process.Completed = $true
}
모의 프로세스가 작업으로 Foreach-Object 전송되고 시작됩니다. ThrottleLimit는 큐에서 실행되는 여러 프로세스를 강조 표시하기 위해 3으로 설정됩니다. 작업은 변수에 $job 저장되며 나중에 모든 프로세스가 완료된 시기를 알 수 있습니다.
문을 사용하여 using: PowerShell에서 부모 범위 변수를 참조하는 경우 식을 사용하여 동적으로 만들 수 없습니다. 예를 들어 다음과 $process = $using:sync.$($PSItem.id)같이 변수를 $process 만들려고 하면 식을 사용할 수 없다는 오류가 발생합니다. 따라서 변수가 $syncCopy 실패할 위험 없이 변수를 참조하고 수정 $sync 할 수 있도록 변수를 만듭니다.
다음으로, 동기화된 해시 테이블 키를 참조하여 변수를 사용하여 $process 현재 루프에 있는 프로세스의 진행률을 나타내는 해시 테이블을 빌드합니다. 작업 및 상태 키는 다음 섹션에서 지정된 모의 프로세스의 상태를 표시하기 위한 Write-Progress 매개 변수 값으로 사용됩니다.
foreach 루프는 프로세스 작동을 시뮬레이트하는 한 가지 방법일 뿐이며 밀리초를 사용하여 Start-Sleep을 설정하는 $dataSet Wait 특성에 따라 무작위로 지정됩니다. 프로세스의 진행률을 계산하는 방법은 다를 수 있습니다.
여러 프로세스의 진행률 표시
모의 프로세스가 작업으로 실행되었으므로 이제 프로세스 진행률을 PowerShell 창에 쓰기 시작할 수 있습니다.
while($job.State -eq 'Running')
{
$sync.Keys | Foreach-Object {
# If key is not defined, ignore
if(![string]::IsNullOrEmpty($sync.$_.keys))
{
# Create parameter hashtable to splat
$param = $sync.$_
# Execute Write-Progress
Write-Progress @param
}
}
# Wait to refresh to not overload gui
Start-Sleep -Seconds 0.1
}
$job 변수는 부모 작업을 포함하며 각 모의 프로세스의 자식 작업을 포함합니다. 자식 작업이 계속 실행되는 동안 부모 작업 상태는 "실행 중"으로 유지됩니다. 이렇게 하면 루프를 while 사용하여 모든 프로세스가 완료될 때까지 모든 프로세스의 진행률을 지속적으로 업데이트할 수 있습니다.
while 루프 내에서 변수의 각 키를 반복합니다 $sync . 이는 동기화된 해시 테이블이므로 지속적으로 업데이트되지만 오류를 throw하지 않고 계속 액세스할 수 있습니다.
보고되는 프로세스가 실제로 메서드를 사용하여 실행되고 있는지 확인하는 검사가 있습니다 IsNullOrEmpty() . 프로세스가 시작되지 않은 경우 루프는 해당 프로세스를 보고하고 시작된 프로세스로 이동할 때까지 다음으로 이동하지 않습니다. 프로세스가 시작되면 현재 키의 해시 테이블을 사용하여 매개 변수를 다음으로 스플래트합니다 Write-Progress.
전체 예제
# Example workload
$dataset = @(
@{
Id = 1
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 2
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 3
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 4
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 5
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
)
# Create a hashtable for process.
# Keys should be ID's of the processes
$origin = @{}
$dataset | Foreach-Object {$origin.($_.id) = @{}}
# Create synced hashtable
$sync = [System.Collections.Hashtable]::Synchronized($origin)
$job = $dataset | Foreach-Object -ThrottleLimit 3 -AsJob -Parallel {
$syncCopy = $using:sync
$process = $syncCopy.$($PSItem.Id)
$process.Id = $PSItem.Id
$process.Activity = "Id $($PSItem.Id) starting"
$process.Status = "Processing"
# Fake workload start up that takes x amount of time to complete
start-sleep -Milliseconds ($PSItem.wait*5)
# Process. update activity
$process.Activity = "Id $($PSItem.id) processing"
foreach ($percent in 1..100)
{
# Update process on status
$process.Status = "Handling $percent/100"
$process.PercentComplete = (($percent / 100) * 100)
# Fake workload that takes x amount of time to complete
Start-Sleep -Milliseconds $PSItem.Wait
}
# Mark process as completed
$process.Completed = $true
}
while($job.State -eq 'Running')
{
$sync.Keys | Foreach-Object {
# If key is not defined, ignore
if(![string]::IsNullOrEmpty($sync.$_.keys))
{
# Create parameter hashtable to splat
$param = $sync.$_
# Execute Write-Progress
Write-Progress @param
}
}
# Wait to refresh to not overload gui
Start-Sleep -Seconds 0.1
}
관련 링크
PowerShell