1

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

$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 中是否可行?

4

3 回答 3

3

我认为这是正确的,基于使用 Foreach Parallel 跨多个线程编写进度它可能缺少一个 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
}

输出:

####################################################################################################
于 2021-05-17T16:48:18.277 回答
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
    }
}

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

于 2021-09-28T00:28:29.153 回答
1

根据本文 - 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.

基本上,您不能分配回变量$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
#

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

于 2021-05-17T14:15:24.613 回答