ค้นหาเว็บไซต์

แสดงความคืบหน้าขณะใช้งานแบบมัลติเธรด


เริ่มต้นใน 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
}

บทความที่เกี่ยวข้อง