Reputation: 73
This code prints a simple progression, doing one thing at a time:
$files = 1..100
$i = 0
$files | Foreach-Object {
$progress = ("#" * $i)
Write-Host "`r$progress" -NoNewLine
$i ++
Start-Sleep -s 0.1
}
But if I want to do two things in parallel at the same time, I can't output the progress because I can't change the variable outside the parallel loops. This doesn't do what's needed:
$files = 1..100
$i = 0
$files | Foreach-Object -ThrottleLimit 2 -Parallel {
$progress = ("#" * $i)
Write-Host "`r$progress" -NoNewLine
$i ++
Start-Sleep -s 0.1
}
I can't find a good solution for accessing an external variable not only to read it with $Using, but also to change it. Is this even possible in Powershell 7?
Upvotes: 7
Views: 11159
Reputation: 1082
I found this solution with thead-safe dictionaries on https://devblogs.microsoft.com/powershell/powershell-foreach-object-parallel-feature/. I think this is at the moment the safest solution:
$threadSafeDictionary = [System.Collections.Concurrent.ConcurrentDictionary[string,object]]::new()
Get-Process | ForEach-Object -Parallel {
$dict = $using:threadSafeDictionary
$dict.TryAdd($_.ProcessName, $_)
}
$threadSafeDictionary["pwsh"]
Upvotes: 2
Reputation: 392
If you are working with larger values of -ThrottleLimit
(say 4+), using a synchronized queue (for thread safety), Write-Progress
, and jobs can be a nice solution for tracking progress. As others have mentioned, the $Using
keywords allows you access to variables within a scriptblock scope:
$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
}
}
In my experience, using a synchronized hashtable was too messy for several threads; I wanted a single, clean progress bar. It depends on your use case though. Thought I'd add my piece to the other excellent answers.
Upvotes: 6
Reputation: 9975
Per this article - PowerShell ForEach-Object Parallel Feature - you can reference variables from the "outer" script using the $using
keyword:
e.g.
$files = 1..100
$i = 100;
$files | Foreach-Object -ThrottleLimit 2 -Parallel {
write-host ($using:i)
Start-Sleep -s .1
}
# 100
# 100
# etc
But if you try to update the value you'll get this:
$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.
Basically, you can't assign back to the $using:i
variable.
What you could do is mutate the properties of a complex object instead - e.g. this:
$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
#
which lets you update the value, but may not (probably won't be) be thread-safe.
Upvotes: 9
Reputation: 27423
I think this is right, based on Writing Progress across multiple threads with Foreach Parallel. It may be missing a lock, but just for writing progress it's probably not a big deal. In this case you can just use the filename for the progress too.
$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
}
Output:
####################################################################################################
Upvotes: 3