当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 多线程时显示进度

作者:精品下载站 日期:2024-12-14 03:03:48 浏览:15 分类:玩电脑

多线程时显示进度


从 PowerShell 7.0 开始,使用 Foreach-Object cmdlet 中的 Parallel 参数可以同时在多个线程中工作。不过,监控这些线程的进度可能是一个挑战。通常,您可以使用 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 键是模拟进程的标识符。 Wait 键模拟被跟踪的每个模拟进程的工作负载。

$origin 变量存储一个嵌套哈希表,每个键都是模拟进程 ID 之一。然后,它用于合并存储在 $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 变量中,使我们能够知道所有进程稍后何时完成。

在 PowerShell 中使用 using: 语句引用父作用域变量时,不能使用表达式使其动态化。例如,如果您尝试创建像这样的 $process 变量,$process=$using:sync.$ ($PSItem.id),您将得到错误表明您不能在那里使用表达式。因此,我们创建 $syncCopy 变量以便能够引用和修改 $sync 变量,而不会有失败的风险。

接下来,我们通过引用同步哈希表键,使用 $process 变量构建一个哈希表来表示当前循环中进程的进度。 ActivityStatus 键用作 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 变量包含每个模拟进程的父 job 和子 job。当任何子作业仍在运行时,父作业状态将保持“正在运行”。这允许我们使用 while 循环不断更新每个进程的进度,直到所有进程完成。

在 while 循环中,我们循环遍历 $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
}

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯