Wha*_*m I 5 powershell foreach-object
此代码打印一个简单的进程,一次只做一件事:
$files = 1..100
$i = 0
$files | Foreach-Object {
$progress = ("#" * $i)
Write-Host "`r$progress" -NoNewLine
$i ++
Start-Sleep -s 0.1
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我想同时并行执行两件事,我无法输出进度,因为我无法更改并行循环之外的变量。这并没有完成所需的操作:
$files = 1..100
$i = 0
$files | Foreach-Object -ThrottleLimit 2 -Parallel {
$progress = ("#" * $i)
Write-Host "`r$progress" -NoNewLine
$i ++
Start-Sleep -s 0.1
}
Run Code Online (Sandbox Code Playgroud)
我找不到一个好的解决方案来访问外部变量,不仅可以使用 $Using 读取它,还可以更改它。这在 Powershell 7 中可能吗?
根据本文 - PowerShell ForEach-Object 并行功能- 您可以使用关键字引用“外部”脚本中的变量$using
:
例如
$files = 1..100
$i = 100;
$files | Foreach-Object -ThrottleLimit 2 -Parallel {
write-host ($using:i)
Start-Sleep -s .1
}
# 100
# 100
# etc
Run Code Online (Sandbox Code Playgroud)
但如果您尝试更新该值,您将得到以下信息:
$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.
Run Code Online (Sandbox Code Playgroud)
基本上,您不能分配回变量$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
#
Run Code Online (Sandbox Code Playgroud)
它允许您更新值,但可能不是(可能不会)是线程安全的。
如果您正在使用较大的值-ThrottleLimit
(例如 4+),则使用同步队列(为了线程安全)、Write-Progress
和 jobs 可能是跟踪进度的一个很好的解决方案。正如其他人提到的,$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
}
}
Run Code Online (Sandbox Code Playgroud)
根据我的经验,使用同步哈希表对于多个线程来说太混乱了;我想要一个单一、干净的进度条。但这取决于您的用例。我想我会将我的作品添加到其他优秀答案中。
我认为这是正确的,基于使用 Foreach Parallel 跨多个线程编写进度。 它可能缺少一个锁,但仅对于写入进度而言,这可能不是什么大问题。在这种情况下,您也可以仅使用文件名来表示进度。
$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
}
Run Code Online (Sandbox Code Playgroud)
输出:
####################################################################################################
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
8541 次 |
最近记录: |