【问题标题】:Powershell Foreach-Object -Parallel how to change the value of a variable outside the loop (track progress)Powershell Foreach-Object -Parallel 如何在循环外更改变量的值(跟踪进度)
【发布时间】:2021-05-17 13:32:32
【问题描述】:

这段代码打印了一个简单的进程,一次只做一件事:

$files = 1..100
$i = 0
$files | Foreach-Object {
    $progress = ("#" * $i)
    Write-Host  "`r$progress" -NoNewLine
    $i ++
    Start-Sleep -s 0.1
}

但是如果我想同时并行做两件事,我无法输出进度,因为我无法在并行循环之外更改变量。这不能满足需要:

$files = 1..100
$i = 0
$files | Foreach-Object -ThrottleLimit 2 -Parallel {
    $progress = ("#" * $i)
    Write-Host  "`r$progress" -NoNewLine
    $i ++
    Start-Sleep -s 0.1
}

我找不到一个好的解决方案来访问外部变量,不仅可以使用 $Using 读取它,还可以更改它。这在 Powershell 7 中是否可行?

【问题讨论】:

  • 一种选择是仅跟踪您可以“从外部”观察到的事物的进度,即。 “Y 个并行作业中的 X 个已完成”
  • 如果你想用线程安全的哈希表解决问题,有一个关于显示多个线程进度的文档:docs.microsoft.com/en-us/powershell/scripting/learn/deep-dives/…
  • @js2010 是的,我见过这个。这不完全是我需要的。老实说,我真的不明白它是如何与同步哈希表一起工作的。就我而言,有一个文件列表,我需要对每个文件进行大量编码。我的电脑可以同时处理 2 个编码过程。我只需要显示已完成文件数量的总体进度。

标签: powershell foreach-object


【解决方案1】:

我认为这是正确的,基于Writing Progress across multiple threads with Foreach ParallelIt may be missing a lock,但只是为了写进度可能没什么大不了的。在这种情况下,您也可以只使用文件名来表示进度。

$hash = @{i = 1}
$sync = [System.Collections.Hashtable]::Synchronized($hash)

$files = 1..100
$files | Foreach-Object -throttlelimit 2 -parallel {
    $syncCopy = $using:sync
    $progress = '#' * $syncCopy.i
    #$progress = '#' * $_
    Write-Host  "`r$progress" -NoNewLine
    $syncCopy.i++
    Start-Sleep .1
}

输出:

####################################################################################################

【讨论】:

    【解决方案2】:

    如果您使用较大的 -ThrottleLimit 值(例如 4+),则使用同步队列(为了线程安全)Write-Progress 和作业可能是跟踪进度的一个很好的解决方案。正如其他人所提到的,$Using 关键字允许您访问脚本块范围内的变量:

    $files = 1..100
    $queue = [System.Collections.Queue]::new()
    1..$files.Count | ForEach-Object { $queue.Enqueue($_) }
    $syncQueue = [System.Collections.Queue]::synchronized($queue)
    
    $job = $files | ForEach-Object -AsJob -ThrottleLimit 6 -Parallel {
        $sqCopy = $Using:syncQueue
        #Simulating work...do stuff with files here
        Start-Sleep (Get-Random -Maximum 10 -Minimum 1)
    
        #Dequeue element to update progress
        $sqCopy.Dequeue()
    }
    
    #While $job is running, update progress bar
    while ($job.State -eq 'Running') {
        if ($syncQueue.Count -gt 0) {
            $status = ((1 / $syncQueue.Count) * 100)
            Write-Progress -Activity "Operating on Files" -Status "Status" -PercentComplete $status
            Start-Sleep -Milliseconds 100
        }
    }
    

    根据我的经验,使用同步哈希表对于多个线程来说太麻烦了;我想要一个单一的、干净的进度条。不过,这取决于您的用例。以为我会将我的作品添加到其他出色的答案中。

    【讨论】:

      【解决方案3】:

      根据这篇文章 - PowerShell ForEach-Object Parallel Feature - 您可以使用 $using 关键字从“外部”脚本中引用变量:

      例如

      $files = 1..100
      $i = 100;
      $files | Foreach-Object -ThrottleLimit 2 -Parallel {
          write-host ($using:i)
          Start-Sleep -s .1
      }
      # 100
      # 100
      # etc
      
      

      但如果你尝试更新值,你会得到:

      $files | Foreach-Object -ThrottleLimit 2 -Parallel {
          $using:i += $using:i
          Start-Sleep -s .1
      }
      
      ParserError:
      Line |
         2 |      $using:i += $using:i
           |      ~~~~~~~~
           | The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept
           | assignments, such as a variable or a property.
      

      基本上,您不能将 back 分配给$using:i 变量。

      可以做的是改变复杂对象的属性 - 例如这个:

      $counter = @{ "i" = 0 }
      $files | Foreach-Object -ThrottleLimit 2 -Parallel {
           ($using:counter).i = ($using:counter).i + 1
           Start-Sleep -s .1
      }
      $counter
      
      # Name                           Value
      # ----                           -----
      # i                              100
      #
      

      它允许您更新值,但可能不是(可能不会)是线程安全的。

      【讨论】:

      • $counter = @{ "i" = 0 } - 是的,它有效。虽然当你做你不应该做的事情时,这看起来像是一种黑客行为。
      • 错字:$using:i += $usinge:i
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多