Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Desde PowerShell 7.0, es posible trabajar en varios subprocesos simultáneamente mediante el parámetro Parallel en el cmdlet Foreach-Object. No obstante, la supervisión del progreso de estos subprocesos puede suponer un reto. Normalmente, puede supervisar el progreso de un proceso mediante Write-Progress.
Pero, debido a que PowerShell usa un espacio de ejecución independiente para cada subproceso al emplear Parallel, informar del progreso al host no es tan sencillo como el uso normal de Write-Progress
.
Uso de una tabla hash sincronizada para realizar un seguimiento del progreso
Al escribir el progreso desde varios subprocesos, el seguimiento resulta difícil porque, al ejecutar procesos paralelos en PowerShell, cada uno tiene su propio espacio de ejecución. Para evitar esto, puede usar una tabla hash sincronizada. Una tabla hash sincronizada es una estructura de datos segura para subprocesos que varios de ellos pueden modificar simultáneamente sin que se produzca un error.
Configurar
Uno de los inconvenientes de este enfoque es que toma una configuración algo compleja para asegurarse de que todo se ejecuta sin errores.
$dataset = @(
@{
Id = 1
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 2
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 3
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 4
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 5
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
)
# Create a hashtable for process.
# Keys should be ID's of the processes
$origin = @{}
$dataset | Foreach-Object {$origin.($_.id) = @{}}
# Create synced hashtable
$sync = [System.Collections.Hashtable]::Synchronized($origin)
En esta sección se crean tres estructuras de datos distintas, para tres propósitos diferentes.
La variable $dataSet
almacena una matriz de tablas hash que se usa para coordinar los pasos siguientes sin el riesgo de que se modifiquen. Si se modifica una colección de objetos mientras se recorre en iteración, se producirá un error en PowerShell. Debe mantener la colección de objetos en el bucle independiente de los objetos que se van a modificar. La clave Id
de las tablas hash es el identificador de un proceso ficticio. La clave Wait
simula la carga de trabajo de los procesos ficticios de los que se realiza un seguimiento.
La variable $origin
almacena una tabla hash anidada, en la que cada clave es uno de los identificadores del proceso ficticio.
A continuación, se usa para hidratar la tabla hash sincronizada almacenada en la variable $sync
. La variable $sync
es responsable de notificar el progreso al espacio de ejecución primario, que muestra el progreso.
Ejecución de los procesos
En esta sección se ejecutan los procesos con varios subprocesos y se crean algunos de los resultados que se usan para mostrar el progreso.
$job = $dataset | Foreach-Object -ThrottleLimit 3 -AsJob -Parallel {
$syncCopy = $using:sync
$process = $syncCopy.$($PSItem.Id)
$process.Id = $PSItem.Id
$process.Activity = "Id $($PSItem.Id) starting"
$process.Status = "Processing"
# Fake workload start up that takes x amount of time to complete
start-sleep -Milliseconds ($PSItem.wait*5)
# Process. update activity
$process.Activity = "Id $($PSItem.id) processing"
foreach ($percent in 1..100)
{
# Update process on status
$process.Status = "Handling $percent/100"
$process.PercentComplete = (($percent / 100) * 100)
# Fake workload that takes x amount of time to complete
Start-Sleep -Milliseconds $PSItem.Wait
}
# Mark process as completed
$process.Completed = $true
}
Los procesos ficticios se envían a Foreach-Object
y se inician como trabajos. El valor de ThrottleLimit se establece en 3 para resaltar la ejecución de varios procesos en una cola. Los trabajos se almacenan en la variable $job
, que nos permite saber cuándo se completan todos los procesos más adelante.
Al utilizar la instrucción using:
para hacer referencia a una variable de ámbito principal en PowerShell, no puede usar expresiones para que sea dinámica. Por ejemplo, si ha intentado crear la variable $process
de este modo ($process = $using:sync.$($PSItem.id)
), se producirá un error en el que se le indicará que no puede utilizar expresiones en ese lugar. Por lo tanto, creamos la variable $syncCopy
para poder hacer referencia a la variable $sync
y modificarla sin el riesgo de que se produzca un error.
A continuación, creamos una tabla hash para representar el progreso del proceso actualmente en el bucle con la variable $process
. Para ello, hacemos referencia a las claves de la tabla hash sincronizadas. Las claves Activity y Status se utilizan como valores de parámetro para que Write-Progress
muestre el estado de un proceso ficticio determinado en la sección siguiente.
El bucle foreach
solo es una forma de simular el funcionamiento del proceso y se aleatoriza en función del atributo $dataSet
Wait para establecer Start-Sleep
usando milisegundos. La forma de calcular el progreso del proceso puede variar.
Vista del progreso de varios procesos
Ahora que los procesos ficticios se están ejecutando como trabajos, podemos empezar a escribir el progreso de los procesos en la ventana de PowerShell.
while($job.State -eq 'Running')
{
$sync.Keys | Foreach-Object {
# If key is not defined, ignore
if(![string]::IsNullOrEmpty($sync.$_.keys))
{
# Create parameter hashtable to splat
$param = $sync.$_
# Execute Write-Progress
Write-Progress @param
}
}
# Wait to refresh to not overload gui
Start-Sleep -Seconds 0.1
}
La variable $job
contiene el trabajo principal y tiene un trabajo secundario para cada uno de los procesos ficticios. Mientras haya algún trabajo secundario en ejecución, el valor de State del trabajo principal será "Running". Esto nos permite usar el bucle while
para actualizar continuamente el progreso de todos los procesos hasta que finalicen.
Dentro del bucle while, se recorren todas las claves de la variable $sync
. Dado que se trata de una tabla hash sincronizada, se actualiza constantemente, pero todavía se puede acceder a ella sin que se produzcan errores.
Se realiza una comprobación para asegurarse de que el proceso del que se está informando se está ejecutando realmente con el método IsNullOrEmpty()
. Si el proceso no se ha iniciado, el bucle no informará sobre él y pasará al siguiente hasta que llegue a un proceso que se haya iniciado. Si se inicia el proceso, se usa la tabla hash de la clave actual para expandir los parámetros a Write-Progress
.
Ejemplo completo
# Example workload
$dataset = @(
@{
Id = 1
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 2
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 3
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 4
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 5
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
)
# Create a hashtable for process.
# Keys should be ID's of the processes
$origin = @{}
$dataset | Foreach-Object {$origin.($_.id) = @{}}
# Create synced hashtable
$sync = [System.Collections.Hashtable]::Synchronized($origin)
$job = $dataset | Foreach-Object -ThrottleLimit 3 -AsJob -Parallel {
$syncCopy = $using:sync
$process = $syncCopy.$($PSItem.Id)
$process.Id = $PSItem.Id
$process.Activity = "Id $($PSItem.Id) starting"
$process.Status = "Processing"
# Fake workload start up that takes x amount of time to complete
start-sleep -Milliseconds ($PSItem.wait*5)
# Process. update activity
$process.Activity = "Id $($PSItem.id) processing"
foreach ($percent in 1..100)
{
# Update process on status
$process.Status = "Handling $percent/100"
$process.PercentComplete = (($percent / 100) * 100)
# Fake workload that takes x amount of time to complete
Start-Sleep -Milliseconds $PSItem.Wait
}
# Mark process as completed
$process.Completed = $true
}
while($job.State -eq 'Running')
{
$sync.Keys | Foreach-Object {
# If key is not defined, ignore
if(![string]::IsNullOrEmpty($sync.$_.keys))
{
# Create parameter hashtable to splat
$param = $sync.$_
# Execute Write-Progress
Write-Progress @param
}
}
# Wait to refresh to not overload gui
Start-Sleep -Seconds 0.1
}