Background jobs são um recurso essencial do PowerShell que permite executar comandos ou scripts de forma assíncrona, liberando o console para outras tarefas enquanto o trabalho é processado em segundo plano. Isso é particularmente útil para operações demoradas, como chamadas a serviços web, processamento de arquivos grandes ou tarefas de manutenção de sistema. Nesta aula, vamos explorar os cmdlets principais para criar e gerenciar jobs, entender as diferenças entre jobs e runspaces, e ver casos de uso realistas.

Dominar background jobs é um passo importante para escrever scripts mais eficientes e responsivos. Você aprenderá a iniciar tarefas em paralelo, coletar resultados e lidar com erros de forma controlada. Vamos começar com o básico: o cmdlet Start-Job.

Start-Job

O cmdlet Start-Job inicia um job em segundo plano. Ele cria um novo processo do PowerShell para executar o script ou comando fornecido, retornando um objeto de job que representa a tarefa. O job é executado de forma assíncrona, e o console fica imediatamente disponível para outros comandos.

A sintaxe básica é simples: Start-Job -ScriptBlock { ... }. Você também pode passar argumentos usando o parâmetro -ArgumentList. O job retornado pode ser armazenado em uma variável para posterior recuperação de resultados.

Aqui está um exemplo simples que inicia um job que espera 5 segundos e retorna uma mensagem:

$job = Start-Job -ScriptBlock { Start-Sleep -Seconds 5; 'Job concluído' }
Write-Host "Job ID: $($job.Id)"  # Exibe o ID do job
Write-Host "Estado: $($job.State)"  # Exibe Running

Você pode iniciar vários jobs simultaneamente para executar tarefas em paralelo. Por exemplo, para baixar vários arquivos ao mesmo tempo:

$urls = @('https://exemplo.com/a.txt', 'https://exemplo.com/b.txt', 'https://exemplo.com/c.txt')
$jobs = foreach ($url in $urls) {
    Start-Job -ScriptBlock { param($u) Invoke-WebRequest -Uri $u -OutFile (Split-Path $u -Leaf) } -ArgumentList $url
}
$jobs | Wait-Job

O cmdlet Wait-Job aguarda a conclusão de todos os jobs especificados. Sem ele, os jobs continuariam em segundo plano.

Receive-Job

Depois que um job é concluído, você precisa recuperar os resultados usando Receive-Job. Esse cmdlet obtém a saída gerada pelo job, incluindo qualquer erro ou aviso. É importante chamá-lo para consumir os resultados e liberar recursos do job.

O uso básico é Receive-Job -Job $job. Você pode especificar o parâmetro -Keep para manter a saída no job depois de recuperá-la, útil se precisar acessá-la novamente. Sem -Keep, a saída é removida após a leitura.

$job = Start-Job -ScriptBlock { Get-Service | Select-Object -First 3 }
Wait-Job $job
Receive-Job $job -Keep

Para jobs que geram muitos dados, você pode processar a saída diretamente: Receive-Job $job | Out-File resultado.txt. Se houver erros, eles são capturados no stream de erro do job e podem ser exibidos com Receive-Job -ErrorAction Continue ou acessados via propriedade ChildJobs[0].Error.

$job = Start-Job -ScriptBlock { throw 'Erro proposital' }
Wait-Job $job
Receive-Job $job  # Exibe o erro
$job.ChildJobs[0].Error  # Acessa o objeto de erro

Lembre-se de remover jobs concluídos com Remove-Job para liberar memória, especialmente em scripts longos.

Jobs vs runspaces

Jobs tradicionais criam um novo processo do PowerShell para cada job, o que é robusto e isolado, mas tem sobrecarga de inicialização. Runspaces, por outro lado, são threads dentro do mesmo processo, mais rápidos e leves, mas exigem mais cuidado com sincronização e compartilhamento de estado.

Para a maioria dos scripts administrativos, jobs são suficientes e mais simples de usar. Eles oferecem isolamento completo: cada job tem seu próprio ambiente, o que evita conflitos de variáveis e módulos. No entanto, se você precisa de desempenho máximo ou deseja compartilhar dados entre tarefas, runspaces podem ser mais adequados.

Exemplo de comparação de desempenho: iniciar 10 jobs vs 10 runspaces. Jobs levam mais tempo para iniciar, mas são mais seguros. Runspaces podem ser criados com a classe [System.Management.Automation.Runspaces.Runspace] e exigem mais código.

# Criando um runspace simples
$ps = [System.Management.Automation.PowerShell]::Create()
$ps.AddScript({ Start-Sleep -Seconds 2; 'Runspace done' })
$async = $ps.BeginInvoke()
# Aguarda e obtém resultado
$result = $ps.EndInvoke($async)
$ps.Dispose()

Em geral, use jobs para simplicidade e isolamento; use runspaces quando precisar de alta concorrência e baixa sobrecarga, e esteja disposto a lidar com complexidades adicionais.

Casos de uso

Background jobs são ideais para tarefas que não precisam de interação imediata com o usuário. Exemplos comuns:

  • Backup de arquivos: copiar grandes volumes de dados em segundo plano enquanto o usuário continua trabalhando.
  • Monitoramento de sistema: executar coletas de métricas periódicas sem bloquear o console.
  • Processamento em lote: converter arquivos, redimensionar imagens, processar logs.
  • Consultas a APIs externas: fazer chamadas demoradas a serviços web e processar respostas quando prontas.
  • Simulações ou testes: executar testes de carga em paralelo para acelerar a execução.

Um exemplo prático de uso em script: iniciar jobs para verificar a disponibilidade de vários servidores via ping, coletando resultados após a conclusão:

$servers = 'server1', 'server2', 'server3'
$jobs = foreach ($server in $servers) {
    Start-Job -ScriptBlock { param($s) Test-Connection -ComputerName $s -Count 2 -Quiet } -ArgumentList $server
}
$results = $jobs | Wait-Job | Receive-Job
for ($i = 0; $i -lt $servers.Count; $i++) {
    Write-Host "$($servers[$i]): $($results[$i])"
}

Outro caso é usar jobs para baixar múltiplos arquivos simultaneamente, reduzindo o tempo total de download. Em scripts de automação, você pode iniciar jobs para executar tarefas de manutenção em segundo plano e gerar relatórios ao final.

Boas práticas

  • Sempre use Wait-Job antes de Receive-Job para garantir que o job terminou.
  • Use -Keep se precisar acessar a saída mais de uma vez.
  • Remova jobs com Remove-Job após a coleta para liberar recursos.
  • Capture e trate erros dentro do script block para evitar falhas silenciosas.
  • Considere usar jobs em conjunto com ForEach-Object -Parallel (PowerShell 7+) para paralelismo mais simples em alguns cenários.

Referências

Exercícios

  1. Crie um job que execute Get-Process e retorne os 5 processos com maior uso de CPU. Espere o job concluir e exiba o resultado.
  2. ✓ Resposta:
    $job = Start-Job -ScriptBlock { Get-Process | Sort-Object CPU -Descending | Select-Object -First 5 }
    Wait-Job $job
    Receive-Job $job
  3. Inicie três jobs em paralelo que cada um calcule a soma de 1 a 1000 (use um loop). Aguarde todos e some os resultados para obter o total.
  4. ✓ Resposta:
    $jobs = 1..3 | ForEach-Object { Start-Job -ScriptBlock { $sum = 0; 1..1000 | ForEach-Object { $sum += $_ }; $sum } }
    $results = $jobs | Wait-Job | Receive-Job
    $total = ($results | Measure-Object -Sum).Sum
    Write-Host "Total: $total"
  5. Crie um job que tente acessar um arquivo inexistente e capture o erro. Exiba a mensagem de erro usando Receive-Job.
  6. ✓ Resposta:
    $job = Start-Job -ScriptBlock { Get-Content 'C:\nao_existe.txt' -ErrorAction Stop }
    Wait-Job $job
    $output = Receive-Job $job 2>&1
    $output | Out-String | Write-Host
  7. Escreva um script que use jobs para verificar se três sites estão online (use Invoke-WebRequest). Retorne True/False para cada um.
  8. ✓ Resposta:
    $sites = 'https://google.com', 'https://exemplo.com', 'https://github.com'
    $jobs = foreach ($site in $sites) {
        Start-Job -ScriptBlock { param($url) try { $r = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 5; $true } catch { $false } } -ArgumentList $site
    }
    $results = $jobs | Wait-Job | Receive-Job
    for ($i = 0; $i -lt $sites.Count; $i++) {
        Write-Host "$($sites[$i]): $($results[$i])"
    }
  9. Explique a diferença entre jobs e runspaces e indique um cenário onde runspaces seriam preferíveis.
  10. ✓ Resposta: Jobs criam processos separados, oferecendo isolamento total, mas com maior sobrecarga. Runspaces usam threads no mesmo processo, sendo mais rápidos e leves, mas exigem cuidado com concorrência e compartilhamento de estado. Prefira runspaces quando precisar de máximo desempenho e baixa latência, como em aplicações que processam muitas tarefas em paralelo com necessidade de comunicação frequente entre elas.