แสดงความคืบหน้าขณะใช้งานแบบมัลติเธรด
เริ่มต้นใน PowerShell 7.0 ความสามารถในการทำงานในหลายๆ เธรดพร้อมกันสามารถทำได้โดยใช้พารามิเตอร์ Parallel ใน Foreach-Object cmdlet การติดตามความคืบหน้าของเธรดเหล่านี้อาจเป็นเรื่องท้าทาย โดยปกติ คุณสามารถติดตามความคืบหน้าของกระบวนการได้โดยใช้ Write-Progress อย่างไรก็ตาม เนื่องจาก PowerShell ใช้รันสเปซแยกต่างหากสำหรับแต่ละเธรดเมื่อใช้ ขนาน การรายงานความคืบหน้ากลับไปยังโฮสต์จึงไม่ตรงไปตรงมาเหมือนกับการใช้งาน Write-Progress ปกติ
การใช้แฮชเทเบิลที่ซิงค์เพื่อติดตามความคืบหน้า
เมื่อเขียนความคืบหน้าจากหลายเธรด การติดตามจะกลายเป็นเรื่องยาก เนื่องจากเมื่อเรียกใช้กระบวนการแบบขนานใน PowerShell แต่ละกระบวนการจะมีรันสเปซของตัวเอง เพื่อแก้ไขปัญหานี้ คุณสามารถใช้แฮชเทเบิลแบบซิงโครไนซ์ได้ แฮชเทเบิลที่ซิงค์คือโครงสร้างข้อมูลที่ปลอดภัยของเธรดที่สามารถแก้ไขได้โดยหลายเธรดพร้อมกันโดยไม่เกิดข้อผิดพลาด
ตั้งค่า
ข้อเสียประการหนึ่งของแนวทางนี้คือต้องใช้การตั้งค่าที่ค่อนข้างซับซ้อนเพื่อให้แน่ใจว่าทุกอย่างจะทำงานโดยไม่มีข้อผิดพลาด
$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 ในแต่ละแฮชเทเบิลเป็นตัวระบุสำหรับกระบวนการจำลอง ปุ่ม รอ จำลองปริมาณงานของแต่ละกระบวนการจำลองที่กำลังติดตาม
ตัวแปร $origin จะจัดเก็บแฮชเทเบิลที่ซ้อนกัน โดยแต่ละคีย์เป็นหนึ่งในรหัสกระบวนการจำลอง จากนั้นจะใช้เพื่อเพิ่มความชุ่มชื้นให้กับแฮชเทเบิลที่ซิงโครไนซ์ซึ่งจัดเก็บไว้ในตัวแปร $sync ตัวแปร $sync มีหน้าที่รายงานความคืบหน้ากลับไปยังรันสเปซพาเรนต์ ซึ่งจะแสดงความคืบหน้า
เรียกใช้กระบวนการ
ส่วนนี้จะรันกระบวนการแบบมัลติเธรดและสร้างเอาต์พุตบางส่วนที่ใช้เพื่อแสดงความคืบหน้า
$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 เช่นนี้ $process=$using:sync.$ ($PSItem.id) คุณจะได้รับ ข้อผิดพลาดที่ระบุว่าคุณไม่สามารถใช้สำนวนที่นั่นได้ ดังนั้นเราจึงสร้างตัวแปร $syncCopy เพื่อให้สามารถอ้างอิงและแก้ไขตัวแปร $sync โดยไม่เสี่ยงที่จะล้มเหลว
ต่อไป เราจะสร้างแฮชเทเบิลเพื่อแสดงความคืบหน้าของกระบวนการที่อยู่ในลูปในปัจจุบันโดยใช้ตัวแปร $process โดยอ้างอิงถึงคีย์แฮชเทเบิลที่ซิงโครไนซ์แล้ว ปุ่ม กิจกรรม และ สถานะ ใช้เป็นค่าพารามิเตอร์สำหรับ Write-Progress เพื่อแสดงสถานะของกระบวนการจำลองที่กำหนดในส่วนถัดไป
ลูป foreach เป็นเพียงวิธีการจำลองกระบวนการทำงานและจะถูกสุ่มตามแอตทริบิวต์ $dataSet Wait เพื่อตั้งค่า Start- สลีปโดยใช้มิลลิวินาที วิธีคำนวณความคืบหน้าของกระบวนการของคุณอาจแตกต่างกันไป
แสดงความคืบหน้าของกระบวนการต่างๆ
ขณะนี้กระบวนการจำลองกำลังทำงานเป็นงาน เราสามารถเริ่มเขียนความคืบหน้าของกระบวนการลงในหน้าต่าง 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 loop เราจะวนซ้ำแต่ละคีย์ในตัวแปร $sync เนื่องจากนี่คือแฮชเทเบิลแบบซิงโครไนซ์ จึงมีการอัปเดตอยู่ตลอดเวลาแต่ยังคงสามารถเข้าถึงได้โดยไม่มีข้อผิดพลาดใดๆ
มีการตรวจสอบเพื่อให้แน่ใจว่ากระบวนการที่รายงานกำลังทำงานอยู่จริงโดยใช้เมธอด 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
}