Jobs y paralelismo

Introducción

Cuando un proceso tarda demasiado, solemos impacientarnos por obtener el resultado. Cuando es necesario ejecutar un segundo proceso, lo habitual es abrir una segunda consola de PowerShell. Pero si el segundo proceso está vinculado a todos los objetos y variables que se recuperaron o crearon justo antes de ejecutar el primero, ¿cómo proceder?

Es posible establecer este primer proceso como un trabajo en segundo plano, y luego pasar directamente al segundo. En PowerShell, esta función se denomina Jobs (trabajo). Esta función se añadió en la versión 2.0 de PowerShell y desde entonces ha incorporado múltiples mejoras funcionales.

El lanzamiento de jobs permite recuperar la consola directamente y seguir trabajando. La necesidad de utilizar jobs para realizar varias tareas de forma asíncrona se debe a que PowerShell, por defecto, solo utiliza un núcleo de procesador y un solo hilo de ejecución. La mayoría de los jobs se ejecutan en una nueva instancia de PowerShell con su propio proceso powershell.exe dedicado. Sin embargo, veremos que hay una excepción.

Ahora se pueden utilizar varios tipos de jobs:

  • BackgroundJob: trabajos en segundo plano. Se ejecutan desde el comando Start-Job.

  • ThreadJob: trabajos en segundo plano basados en múltiples threads (hilos), a diferencia de los backgroundJob, que utilizan múltiples procesos. Este tipo requiere la instalación...

Trabajo de fondo local (BackgroundJob)

El mecanismo que vamos a examinar aquí se refiere a los jobs locales. Es posible lanzar un proceso largo en segundo plano y recuperar el control de la consola de inmediato. Los resultados de los jobs se recuperan posteriormente.

El otro caso de uso es subdividir un trabajo global en varios más pequeños. Supongamos que se necesita exportar o sincronizar datos entre dos bases de datos o directorios. Si las diferencias son muy grandes, tardará varias horas o incluso varios días. Múltiples procesos asíncronos con programación pueden reducir el tiempo de ejecución.

Los jobs son procesos secundarios cuyo proceso principal es la consola desde la que se ejecutan. Si el proceso principal se detiene alguna vez, todos los procesos secundarios también se detienen. Esto también significa que los resultados de un job solo pueden ser recuperados desde la consola que lo lanzó. Los resultados no se escriben en disco (excepto para PSWorkflowJoby RemoteJob). 

Hay varios comandos disponibles para interactuar con los jobs:

Comando

Descripción

Start-Job

Este comando crea e inicia un job en segundo plano.

Get-Job

Este comando recupera todos los jobs presentes, independientemente de su estado.

Receive-Job

Este comando recupera los resultados de uno o más jobs.

Remove-Job

Este comando borra los jobs que han finalizado. Para que un job se borre sin problemas, primero debe haber recuperado el resultado con Receive-Job.

Stop-Job

Este comando se utiliza para detener una tarea en segundo plano.

Wait-Job

Este comando se utiliza para esperar a que finalicen uno o varios jobs antes de entregarlos al usuario en la consola.

En las primeras versiones de PowerShell 2.0, los jobs locales se basaban en el mismo mecanismo que los jobs remotos. Utilizaban un flujo de interacción con WS-Management (o WSMan para Web Service Management). Debido a un comportamiento errático, este proceso se ha retirado.

1. Creación de un primer job

Ejemplo de creación de un job sencillo con el comando Start-Job y Get-HotFix:

PS > Start-Job -Scriptblock {Get-Hotfix}  
  
Id  Name  PSJobTypeName   State   HasMoreData  Location  Command  
--  ----  -------------   -----   -----------  --------  -------  ...

Trabajo de fondo basado en threads (ThreadJob)

Los ThreadJobs, a diferencia de los BackgroundJobs, utilizan múltiples threads para ejecutar el job, mientras que los BackgroundJobs utilizan múltiples procesos.

Gracias a esta diferencia, los ThreadJobs ofrecen un rendimiento significativamente mejor. Los jobs se inician y finalizan en menos tiempo.

Los ThreadJobs no están disponibles de forma predeterminada en PowerShell. Es necesario instalar el módulo ThreadJob desde la Galería de PowerShell:

PS > Install-Module ThreadJob 

1. Uso de los ThreadJobs

Una vez instalado el módulo, puede ver los comandos que contiene:

PS > Get-Command -Module ThreadJob  
  
CommandType Name            Version Source  
----------- ----            ------- ------  
Cmdlet      Start-ThreadJob 2.0.3   Threadjob 

El módulo tiene un único comando, Start-ThreadJob. Es el equivalente de Start-Job para BackgroundJob.

Esta es la única diferencia en el uso entre ambos tipos de job. Para el resto, se aplica la misma gestión que para BackgroundJob.

Volvamos al ejemplo anterior:

PS > Start-ThreadJob -ScriptBlock {  
   1..60 | ForEach-Object {  
      Write-Output...

Jobs remotos (RemoteJob)

Una de las principales capacidades de los jobs es que pueden lanzarse en sistemas remotos. Estos trabajos se crean utilizando el comando Invokey su parámetro -AsJob.

Caso especial con los comandos WMI: es posible ejecutarlos en sistemas remotos. Sin embargo, los trabajos conservarán el tipo WMIJob.

Para que el comando Invoke-Command funcione correctamente, es imprescindible que la conexión remota a través de WinRM esté correctamente configurada entre los sistemas.

Los RemoteJobs tienen una estructura padre/hijo. Cada hijo representa un host remoto y un proceso dedicado.

En cuanto a los errores, si uno de los trabajos hijo tiene un error, el trabajo padre también lo tiene. Sin embargo, esto no impide que los demás trabajos hijos continúen su ejecución.

1. Iniciar un job remoto

Para ello se utiliza el comando Invoke-Command. Puede ejecutarse utilizando un nombre de host o a través de una sesión existente. Por lo general, es preferible utilizar una sesión cuando es necesario llamar varios comandos Invoke-Command. Esto se debe a que este comando crea, utiliza y destruye una sesión remota de PowerShell cuando se utiliza con -ComputerName.

En este caso, solo se utiliza un comando:

PS > $SBRem = {Get-NetAdapter}   
PS > Invoke-Command -ComputerName SRVDC01 -ScriptBlock $SBRem `  
-AsJob  
  
Id  Name  PSJobTypeName State     HasMoreData   Location   Command  ...

Trabajos programados (PSScheduledJob)

Imaginemos que un proceso se ejecuta durante varias horas, a horas concretas, una o dos veces al día. Podría verse tentado a configurar una tarea programada. El problema es que el scripting PowerShell no es nativo de la aplicación y requiere que se llame al ejecutable powershell.exe. Sin embargo, existe una alternativa. Desde la versión 3.0, es posible crear tareas programadas que admiten totalmente la ejecución de código PowerShell.

Este tipo de job puede gestionarse mediante los comandos contenidos en el módulo PSScheduledJob:

PS C:\Users\Nicol> Get-Command -module  PSScheduledJob  
  
CommandType   Name                      Version    Source  
-----------   ----                      -------    ------  
Cmdlet        Add-JobTrigger            1.1.0.0    PSScheduledJob  
Cmdlet        Disable-JobTrigger        1.1.0.0    PSScheduledJob  
Cmdlet        Disable-ScheduledJob      1.1.0.0    PSScheduledJob  
Cmdlet        Enable-JobTrigger         1.1.0.0    PSScheduledJob  
Cmdlet        Enable-ScheduledJob       1.1.0.0    PSScheduledJob  
Cmdlet        Get-JobTrigger            1.1.0.0    PSScheduledJob  
Cmdlet        Get-ScheduledJob          1.1.0.0    PSScheduledJob  
Cmdlet        Get-ScheduledJobOption    1.1.0.0    PSScheduledJob  ...

Workflows (PSWorkflowJob)

El workflow es un tipo especial de job. Permite ejecutar trabajos en segundo plano, ya sea de forma local o remota. Los mecanismos implicados se explican con más detalle en el capítulo siguiente.

Si tomamos el ejemplo de la creación de archivos y lo adaptamos a un workflow, obtenemos:

PS > workflow CompressNBALogs {  
    $LogPath = "$env:ProgramData\MyApp\Logs"  
    $ArchivePath = Join-Path $LogPath `  
        "LogArviches$(Get-Date 'yyyy-MM-dd-HH-mm')"  
    $LogFiles = Get-ChildItem -Path $LogPath -File `  
        -Filter '*.log'  
    Write-Output "Fichiers logs trouves : $LogFiles"  
    $LogFiles | Compress-Archive -DestinationPath $ArchivePath  
    Write-Output 'Fin de la compresión'  
    $LogFiles | Remove-Item -Force  
    Write-Output 'Fin del borrado'  
} 

Para ejecutar el flujo de trabajo, basta con llamarlo añadiendo el parámetro -AsJob:

PS > CompressNBAJob -AsJob  
Id Name PSJobTypeName  State   HasMoreData Location  Command  ...

ForEach-Object

En la versión 7.2 de PowerShell Core, es posible el paralelismo en las acciones del comando ForEach-Object.

Esto facilita el paralelismo en tareas sencillas, como probar conexiones a varios puertos de una máquina:

PS > 80,5001,5000,445 | ForEach-Object -Parallel { Test-NetConnection  
-ComputerName 192.168.2.100 -Port $_ } 

Para esta prueba, puede utilizar el comando Measure-Command para ver los beneficios. En este caso, el tiempo de ejecución se divide por cuatro, porque simplemente hay cuatro puertos que probar.

PowerShell API

Hay un último elemento que permite el paralelismo en las acciones PowerShell. Esto está dirigido principalmente a los desarrolladores. Sin embargo, siempre es una buena idea tener varias cuerdas a su arco. Se trata simplemente de una introducción.

En términos generales, una aplicación utiliza una API (Application Programming Interface) para interactuar con una tecnología determinada. PowerShell tiene su propia API. Permite a aplicaciones de terceros utilizar el motor PowerShell para lanzar un comando o un bloque de script. Es fácil imaginar un sitio web desarrollado en ASP.NET haciendo llamadas a PowerShell en el sistema anfitrión.

Es posible llamar a esta API directamente desde la consola PowerShell y ejecutar procesos asíncronos. El acelerador [powershell] se utiliza para interactuar con la API. Se pueden realizar acciones creando un runspace dedicado al procesamiento.

PS > $RS = [powershell]::Create() 

En primer lugar, instanciamos el objeto.

PS > $RS.AddComand("Get-NetAdapter")  
PS > $RS.Invoke()  
  
Caption                                          :  
Description                                      :  ...