Background jobs
LidoEsta aula ensina como usar background jobs no PowerShell para executar tarefas em segundo plano, cobrindo os cmdlets Start-Job e Receive-Job, diferenças entre jobs e runspaces, e casos de uso práticos. Inclui exemplos de código, boas práticas e exercícios com respostas.
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 RunningVocê 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-JobO 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 -KeepPara 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 erroLembre-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-Jobantes deReceive-Jobpara garantir que o job terminou. - Use
-Keepse precisar acessar a saída mais de uma vez. - Remova jobs com
Remove-Jobapó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
- Start-Job - Microsoft Learn
- Receive-Job - Microsoft Learn
- Wait-Job - Microsoft Learn
- Remove-Job - Microsoft Learn
- Everything about jobs - Microsoft Learn
- Everything about runspaces - Microsoft Learn
- Running multiple jobs - Microsoft Learn
Exercícios
- Crie um job que execute
Get-Processe retorne os 5 processos com maior uso de CPU. Espere o job concluir e exiba o resultado. - 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.
- Crie um job que tente acessar um arquivo inexistente e capture o erro. Exiba a mensagem de erro usando Receive-Job.
- Escreva um script que use jobs para verificar se três sites estão online (use Invoke-WebRequest). Retorne True/False para cada um.
- Explique a diferença entre jobs e runspaces e indique um cenário onde runspaces seriam preferíveis.
$job = Start-Job -ScriptBlock { Get-Process | Sort-Object CPU -Descending | Select-Object -First 5 }
Wait-Job $job
Receive-Job $job$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"$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$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])"
}