|
<#PSScriptInfo |
|
.VERSION 1.0 |
|
.GUID bfb939b9-03f0-433e-ad0f-e4e12f4a009c |
|
.AUTHOR Lee Holmes |
|
#> |
|
<# |
|
.DESCRIPTION |
|
Example implementation of producer / consumer parallelism in PowerShell |
|
#> |
|
[Console]::TreatControlCAsInput = $true |
|
|
|
if (!$args[0]) { |
|
Write-Host "No Folder specified!" |
|
Exit |
|
} |
|
|
|
## The script block we want to run in parallel. Threads will all |
|
## retrieve work from $InputQueue, and send results to $OutputQueue |
|
$parallelScript = { |
|
param( |
|
## An Input queue of work to do |
|
$InputQueue, |
|
|
|
## The output buffer to write responses to |
|
$OutputQueue, |
|
|
|
## State tracking, to help threads communicate |
|
## how much progress they've made |
|
$OutputProgress, $ThreadId, $ShouldExit, |
|
|
|
## Script location |
|
$ScriptPath |
|
) |
|
|
|
## Continually try to fetch work from the input queue, until |
|
## the 'ShouldExit' flag is set |
|
$processed = 0 |
|
$workItem = $null |
|
while(! $ShouldExit.Value) |
|
{ |
|
if ([Console]::KeyAvailable){ |
|
$readkey = [Console]::ReadKey($true) |
|
if ($readkey.Modifiers -eq "Control" -and $readkey.Key -eq "C"){ |
|
$OutputQueue.Enqueue("Ctrl-C pressed...") |
|
return |
|
} |
|
} |
|
|
|
if($InputQueue.TryDequeue([ref] $workItem)) |
|
{ |
|
## If we got a work item, do something with it. |
|
$worker_name = "worker${PID}_$ThreadId" |
|
$script = "$ScriptPath\convert_gif_to_webp_webm_worker.ps1" |
|
#Try |
|
#{ |
|
#Invoke-Expression "$script $worker_name `"$workItem`"" |
|
& $script $worker_name $workItem |
|
#} |
|
#Catch |
|
#{ |
|
# $OutputQueue.Enqueue($_.Exception.Message) |
|
#} |
|
|
|
$workItemResult = "$LastExitCode $workItem" |
|
|
|
## Add the result to the output queue |
|
$OutputQueue.Enqueue($workItemResult) |
|
|
|
## Update our progress |
|
$processed++ |
|
$OutputProgress[$ThreadId] = $processed |
|
} |
|
else |
|
{ |
|
## If there was no work, wait a bit for more. |
|
Start-Sleep -m 100 |
|
} |
|
} |
|
} |
|
|
|
## Create a set of background PowerShell instances to do work, based on the |
|
## number of available processors. |
|
#$threads = Get-WmiObject Win32_Processor | Foreach-Object NumberOfLogicalProcessors |
|
$threads = 4 |
|
$runspaces = 1..$threads | Foreach-Object { [PowerShell]::Create() } |
|
$outputProgress = New-Object 'Int[]' $threads |
|
$inputQueue = New-Object 'System.Collections.Concurrent.ConcurrentQueue[String]' |
|
$outputQueue = New-Object 'System.Collections.Concurrent.ConcurrentQueue[String]' |
|
$shouldExit = $false |
|
$scriptPath = $PSScriptRoot |
|
|
|
## Spin up each of our PowerShell runspaces. Once invoked, these are actively |
|
## waiting for work and consuming once available. |
|
for($counter = 0; $counter -lt $threads; $counter++) |
|
{ |
|
$null = $runspaces[$counter].AddScript($parallelScript). |
|
AddParameter("InputQueue", $inputQueue). |
|
AddParameter("OutputQueue", $outputQueue). |
|
AddParameter("OutputProgress", $outputProgress). |
|
AddParameter("ThreadId", $counter). |
|
AddParameter("ShouldExit", [ref] $shouldExit). |
|
AddParameter("ScriptPath", $scriptPath).BeginInvoke() |
|
} |
|
|
|
## Qeueu some work |
|
$path = $args[0] |
|
$estimated = 0 |
|
foreach ($item in Get-ChildItem $path -Recurse -Filter *.gif) |
|
{ |
|
if (!$item.FullName.Contains("\filepaths_to_exclude\") -and |
|
!$item.FullName.Contains("skip")) |
|
{ |
|
$currentInput = $item.FullName |
|
$inputQueue.Enqueue($currentInput) |
|
$estimated++ |
|
} |
|
} |
|
|
|
## Wait for our worker threads to complete processing the |
|
## work. |
|
try |
|
{ |
|
do |
|
{ |
|
## Update the status of how many items we've processed, based on adding up the |
|
## output progress from each of the worker threads |
|
$totalProcessed = $outputProgress | Measure-Object -Sum | Foreach-Object Sum |
|
if ($estimated -gt 0) |
|
{ |
|
Write-Progress "Processed $totalProcessed of $estimated" -PercentComplete ($totalProcessed * 100 / $estimated) |
|
} |
|
|
|
## If there were any results, output them. |
|
$scriptOutput = $null |
|
while($outputQueue.TryDequeue([ref] $scriptOutput)) |
|
{ |
|
$scriptOutput |
|
} |
|
|
|
## If the threads are done processing the input we gave them, let them know they can exit |
|
if($inputQueue.Count -eq 0) |
|
{ |
|
$shouldExit = $true |
|
} |
|
|
|
Start-Sleep -m 100 |
|
|
|
## See if we still have any busy runspaces. If not, exit the loop. |
|
$busyRunspaces = $runspaces | Where-Object { $_.InvocationStateInfo.State -ne 'Complete' } |
|
} while($busyRunspaces) |
|
} |
|
finally |
|
{ |
|
## Clean up our PowerShell instances |
|
foreach($runspace in $runspaces) |
|
{ |
|
$runspace.Stop() |
|
$runspace.Dispose() |
|
} |
|
} |