about_Thread_Jobs

Descripción breve

Proporciona información sobre los trabajos basados en subprocesos de PowerShell. Un trabajo de subproceso es un tipo de trabajo en segundo plano que ejecuta un comando o expresión en un subproceso independiente dentro del proceso de sesión actual.

Descripción larga

PowerShell ejecuta comandos y scripts simultáneamente a través de trabajos. PowerShell proporciona tres tipos de trabajos para admitir la simultaneidad.

  • RemoteJob - Los comandos y scripts se ejecutan en una sesión remota. Para obtener información, consulte about_Remote_Jobs.
  • BackgroundJob - Los comandos y scripts se ejecutan en un proceso independiente en la máquina local. Para más información, consulte about_Jobs (Acerca de los trabajos).
  • PSTaskJob o ThreadJob bien: los comandos y los scripts se ejecutan en un subproceso independiente dentro del mismo proceso en el equipo local.

Los trabajos basados en subprocesos no son tan sólidos como los trabajos remotos y en segundo plano, ya que se ejecutan en el mismo proceso en subprocesos diferentes. Si un trabajo tiene un error crítico que bloquea el proceso, se finalizan todos los demás trabajos del proceso.

Sin embargo, los trabajos basados en subprocesos requieren menos sobrecarga. No usan la capa de comunicación remota ni la serialización. Los objetos de resultado se devuelven como referencias a objetos activos en la sesión actual. Sin esta sobrecarga, los trabajos basados en subprocesos se ejecutan más rápido y usan menos recursos que los otros tipos de trabajo.

Importante

La sesión primaria que creó el trabajo también supervisa el estado del trabajo y recopila datos de canalización. El proceso primario finaliza el proceso secundario del trabajo una vez que el trabajo alcanza un estado finalizado. Si se finaliza la sesión primaria, todos los trabajos secundarios en ejecución finalizan junto con sus procesos secundarios.

Hay dos maneras de solucionar esta situación:

  1. Use Invoke-Command para crear trabajos que se ejecutan en sesiones desconectadas. Para obtener más información, consulte about_Remote_Jobs.
  2. Use Start-Process para crear un nuevo proceso en lugar de un trabajo. Para obtener más información, vea Start-Process.

Inicio y administración de trabajos basados en subprocesos

Hay dos maneras de iniciar trabajos basados en subprocesos:

  • Start-ThreadJob: desde el módulo ThreadJob
  • ForEach-Object -Parallel -AsJob : la característica paralela se agregó en PowerShell 7.0.

Use los mismos cmdlets de trabajo descritos en about_Jobs para administrar trabajos basados en subprocesos.

Usar Start-ThreadJob

El módulo ThreadJob se incluye primero con PowerShell 6. También se puede instalar desde el Galería de PowerShell para Windows PowerShell 5.1.

Para iniciar un trabajo de subproceso en el equipo local, use el Start-ThreadJob cmdlet con un comando o script entre llaves ({ }).

En el ejemplo siguiente se inicia un trabajo de subproceso que ejecuta un Get-Process comando en el equipo local.

Start-ThreadJob -ScriptBlock { Get-Process }

El Start-ThreadJob comando devuelve un ThreadJob objeto que representa el trabajo en ejecución. El objeto de trabajo contiene información útil sobre el trabajo, incluido su estado de ejecución actual. Recopila los resultados del trabajo a medida que se generan los resultados.

Usar ForEach-Object -Parallel -AsJob

PowerShell 7.0 agregó un nuevo conjunto de parámetros al ForEach-Object cmdlet . Los nuevos parámetros permiten ejecutar bloques de script en subprocesos paralelos como trabajos de PowerShell.

Puede canalizar datos a ForEach-Object -Parallel. Los datos se pasan al bloque de script que se ejecuta en paralelo. El -AsJob parámetro crea objetos de trabajos para cada uno de los subprocesos paralelos.

El comando siguiente inicia un trabajo que contiene trabajos secundarios para cada valor de entrada canalización al comando. Cada trabajo secundario ejecuta el Write-Output comando con un valor de entrada canalización como argumento.

1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob

El ForEach-Object -Parallel comando devuelve un PSTaskJob objeto que contiene trabajos secundarios para cada valor de entrada canalización. El objeto de trabajo contiene información útil sobre los trabajos secundarios que ejecutan el estado. Recopila los resultados de los trabajos secundarios a medida que se generan los resultados.

Cómo esperar a que un trabajo se complete y recupere los resultados del trabajo

Puede usar cmdlets de trabajo de PowerShell, como Wait-Job y Receive-Job esperar a que se complete un trabajo y, a continuación, devolver todos los resultados generados por el trabajo.

El comando siguiente inicia un trabajo de subproceso que ejecuta un Get-Process comando y, a continuación, espera a que se complete el comando y, por último, devuelve todos los resultados de datos generados por el comando.

Start-ThreadJob -ScriptBlock { Get-Process } | Wait-Job | Receive-Job

El comando siguiente inicia un trabajo que ejecuta un Write-Output comando para cada entrada canalización y, a continuación, espera a que se completen todos los trabajos secundarios y, por último, devuelve todos los resultados de datos generados por los trabajos secundarios.

1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob | Wait-Job | Receive-Job

El Receive-Job cmdlet devuelve los resultados de los trabajos secundarios.

1
3
2
4
5

Dado que cada trabajo secundario se ejecuta en paralelo, no se garantiza el orden de los resultados generados.

Rendimiento del trabajo de subprocesos

Los trabajos de subproceso son más rápidos y ligeros que otros tipos de trabajos. Pero todavía tienen sobrecarga que puede ser grande en comparación con el trabajo que está haciendo.

PowerShell ejecuta comandos y script en una sesión. Solo un comando o script se puede ejecutar a la vez en una sesión. Por lo tanto, al ejecutar varios trabajos, cada trabajo se ejecuta en una sesión independiente. Cada sesión contribuye a la sobrecarga.

Los trabajos de subproceso proporcionan el mejor rendimiento cuando el trabajo que realizan es mayor que la sobrecarga de la sesión usada para ejecutar el trabajo. Hay dos casos para que cumplan estos criterios.

  • El trabajo requiere un uso intensivo del proceso: la ejecución de un script en varios trabajos de subproceso puede aprovechar varios núcleos de procesador y completarse más rápido.

  • El trabajo consta de una espera significativa: un script que dedica tiempo a esperar resultados de E/S o llamadas remotas. La ejecución en paralelo normalmente se completa más rápido que si se ejecuta secuencialmente.

(Measure-Command {
    1..1000 | ForEach { Start-ThreadJob { Write-Output "Hello $using:_" } } | Receive-Job -Wait
}).TotalMilliseconds
36860.8226

(Measure-Command {
    1..1000 | ForEach-Object { "Hello: $_" }
}).TotalMilliseconds
7.1975

En el primer ejemplo anterior se muestra un bucle foreach que crea 1000 trabajos de subproceso para realizar una escritura de cadena simple. Debido a la sobrecarga del trabajo, tarda más de 36 segundos en completarse.

En el segundo ejemplo se ejecuta el ForEach cmdlet para realizar las mismas 1000 operaciones. Esta vez, ForEach-Object se ejecuta secuencialmente, en un único subproceso, sin ninguna sobrecarga de trabajo. Se completa en sólo 7 milisegundos.

En el ejemplo siguiente, se recopilan hasta 5000 entradas para 10 registros del sistema independientes. Dado que el script implica leer una serie de registros, tiene sentido realizar las operaciones en paralelo.

$logNames.count
10

Measure-Command {
    $logs = $logNames | ForEach-Object {
        Get-WinEvent -LogName $_ -MaxEvents 5000 2>$null
    }
}

TotalMilliseconds : 252398.4321 (4 minutes 12 seconds)
$logs.Count
50000

El script se completa a la mitad del tiempo en el que los trabajos se ejecutan en paralelo.

Measure-Command {
    $logs = $logNames | ForEach {
        Start-ThreadJob {
            Get-WinEvent -LogName $using:_ -MaxEvents 5000 2>$null
        } -ThrottleLimit 10
    } | Wait-Job | Receive-Job
}

TotalMilliseconds : 115994.3 (1 minute 56 seconds)
$logs.Count
50000

Trabajos y variables de subproceso

Hay varias maneras de pasar valores a los trabajos basados en subprocesos.

Start-ThreadJob puede aceptar variables que se canalizan al cmdlet, que se pasan al bloque de script a través de la $using palabra clave o que se pasan a través del parámetro ArgumentList .

$msg = "Hello"

$msg | Start-ThreadJob { $input | Write-Output } | Wait-Job | Receive-Job

Start-ThreadJob { Write-Output $using:msg } | Wait-Job | Receive-Job

Start-ThreadJob { param ([string] $message) Write-Output $message } -ArgumentList @($msg) |
  Wait-Job | Receive-Job

ForEach-Object -Parallel acepta canalizaciones en variables y las variables que se pasan directamente al bloque de script a través de la $using palabra clave .

$msg = "Hello"

$msg | ForEach-Object -Parallel { Write-Output $_ } -AsJob | Wait-Job | Receive-Job

1..1 | ForEach-Object -Parallel { Write-Output $using:msg } -AsJob | Wait-Job | Receive-Job

Dado que los trabajos de subproceso se ejecutan en el mismo proceso, cualquier tipo de referencia de variable pasado al trabajo debe tratarse cuidadosamente. Si no es un objeto seguro para subprocesos, nunca debe asignarse a él y nunca se deben invocar el método y las propiedades en él.

En el ejemplo siguiente se pasa un objeto .NET ConcurrentDictionary seguro para subprocesos a todos los trabajos secundarios para recopilar objetos de proceso con nombre único. Dado que es un objeto seguro para subprocesos, se puede usar de forma segura mientras los trabajos se ejecutan simultáneamente en el proceso.

$threadSafeDictionary = [System.Collections.Concurrent.ConcurrentDictionary[string,object]]::new()
$jobs = Get-Process | ForEach {
    Start-ThreadJob {
        $proc = $using:_
        $dict = $using:threadSafeDictionary
        $dict.TryAdd($proc.ProcessName, $proc)
    }
}
$jobs | Wait-Job | Receive-Job

$threadSafeDictionary.Count
96

$threadSafeDictionary["pwsh"]

NPM(K)  PM(M)   WS(M) CPU(s)    Id SI ProcessName
------  -----   ----- ------    -- -- -----------
  112  108.25  124.43  69.75 16272  1 pwsh

Consulte también