Runspaces
LidoEsta aula explora runspaces no PowerShell, explicando o que são, como fornecem paralelismo de baixo nível, suas diferenças em relação aos jobs tradicionais e os principais casos de uso, com exemplos práticos e exercícios.
Runspaces são o mecanismo fundamental de execução paralela no PowerShell. Enquanto a maioria dos cmdlets e scripts executam de forma sequencial, runspaces permitem executar blocos de código simultaneamente, aproveitando múltiplos núcleos da CPU. Compreender runspaces é essencial para otimizar scripts que processam grandes volumes de dados ou que realizam operações independentes.
Nesta aula, vamos mergulhar no conceito de runspaces, explorar como eles fornecem paralelismo de baixo nível, compará-los com jobs tradicionais e identificar cenários práticos onde seu uso é vantajoso. Você aprenderá a criar e gerenciar runspaces, além de boas práticas para evitar erros comuns.
O que são
Um runspace é o ambiente de execução onde um comando ou script do PowerShell é executado. Cada runspace possui seu próprio estado, incluindo variáveis, funções e módulos carregados. Por padrão, quando você executa um comando no console, ele roda em um runspace único. Para executar tarefas em paralelo, você pode criar múltiplos runspaces, cada um com seu próprio pipeline e estado.
Os runspaces são criados usando a classe System.Management.Automation.Runspaces.Runspace ou, mais comumente, com o comando CreateRunspace() da classe PowerShell. Você pode abrir um runspace, executar um script e fechá-lo. Para comunicação entre o runspace principal e os filhos, são usados objetos como RunspacePool e PowerShell.
# Criando e usando um runspace simples
$runspace = [runspacefactory]::CreateRunspace()
$runspace.Open()
$ps = [powershell]::Create()
$ps.Runspace = $runspace
$ps.AddScript({ "Executando no runspace: $PID" })
$result = $ps.Invoke()
$result
$ps.Dispose()
$runspace.Close()Neste exemplo, criamos um runspace, abrimos, associamos a uma instância de PowerShell e invocamos um script simples. O resultado é exibido no console. Note que o runspace é independente do runspace principal, então o PID pode ser diferente.
Paralelismo de baixo nível
Runspaces oferecem paralelismo de baixo nível, o que significa que eles operam diretamente com threads do sistema operacional. Quando você cria múltiplos runspaces, eles podem ser executados em threads separados, permitindo que o sistema operacional distribua o trabalho entre núcleos de CPU. Isso é diferente de técnicas de paralelismo de alto nível, como jobs, que envolvem processos separados e comunicação via serialização.
O paralelismo de baixo nível é mais eficiente porque não há overhead de criação de processos completos. No entanto, exige cuidado com concorrência, pois múltiplos threads podem acessar os mesmos recursos simultaneamente. Para gerenciar isso, o PowerShell oferece RunspacePool, que permite limitar o número de runspaces ativos e compartilhar dados de forma controlada.
# Usando RunspacePool para controlar concorrência
$pool = [runspacefactory]::CreateRunspacePool(1, 4) # mínimo 1, máximo 4 runspaces
$pool.Open()
$scripts = 1..10
$psList = foreach ($i in $scripts) {
$ps = [powershell]::Create()
$ps.RunspacePool = $pool
$ps.AddScript("Start-Sleep -Milliseconds 100; Resultado $i")
$ps
}
$results = $psList | ForEach-Object { $_.BeginInvoke() }
# Aguardando e coletando resultados
$output = foreach ($r in $results) {
$r.AsyncWaitHandle.WaitOne()
$_.EndInvoke($r)
}
$output
$psList | ForEach-Object { $_.Dispose() }
$pool.Close()Este exemplo cria um pool com até 4 runspaces ativos e executa 10 scripts. O método BeginInvoke inicia a execução assíncrona, e EndInvoke coleta os resultados. Isso permite processamento paralelo com limite de threads, evitando sobrecarga do sistema.
vs jobs
Jobs no PowerShell (como Start-Job) são uma abstração de mais alto nível para executar tarefas em segundo plano. Cada job é executado em um processo separado, o que fornece isolamento total, mas com custo de performance significativo. Jobs são ideais para tarefas de longa duração ou que precisam de isolamento, mas não para paralelismo intensivo.
Por outro lado, runspaces são mais leves e rápidos, pois compartilham o mesmo processo. Eles são adequados para tarefas que exigem alta performance e onde o overhead de processos seria proibitivo. No entanto, runspaces não oferecem o mesmo nível de isolamento: se um runspace falhar, pode afetar outros. A tabela abaixo resume as diferenças:
| Característica | Runspaces | Jobs |
|---|---|---|
| Execução | Threads no mesmo processo | Processos separados |
| Performance | Alta (baixo overhead) | Baixa (overhead de processo) |
| Isolamento | Parcial (mesmo processo) | Total (processo separado) |
| Comunicação | Direta via objetos .NET | Via serialização (remoting) |
| Uso típico | Processamento paralelo intensivo | Tarefas em background simples |
Em termos práticos, se você precisa processar milhares de itens rapidamente, runspaces são a escolha certa. Se você quer simplesmente executar um script em segundo plano sem bloquear o console, jobs são mais simples.
Casos de uso
Runspaces são particularmente úteis em cenários onde a paralelização pode reduzir drasticamente o tempo de execução. Alguns exemplos:
- Processamento de coleções: iterar sobre milhares de arquivos ou objetos e realizar operações independentes.
- Consultas a serviços externos: fazer múltiplas chamadas HTTP ou consultas a bancos de dados simultaneamente.
- Análise de dados: dividir grandes conjuntos de dados em partes e processá-las em paralelo.
- Scripts de automação: executar várias tarefas de administração do sistema ao mesmo tempo.
Por exemplo, para testar conectividade a vários servidores, você pode usar runspaces para executar Test-Connection em paralelo:
$servers = "server1", "server2", "server3", "server4"
$pool = [runspacefactory]::CreateRunspacePool(1, 4)
$pool.Open()
$psList = foreach ($server in $servers) {
$ps = [powershell]::Create()
$ps.RunspacePool = $pool
$ps.AddScript("Test-Connection -ComputerName $server -Count 1 -Quiet")
[PSCustomObject]@{ Server = $server; PS = $ps }
}
$results = foreach ($item in $psList) {
$handle = $item.PS.BeginInvoke()
[PSCustomObject]@{ Server = $item.Server; Handle = $handle }
}
foreach ($r in $results) {
$r.Handle.AsyncWaitHandle.WaitOne()
$output = $r.PS.EndInvoke($r.Handle)
"$($r.Server): $output"
}
$psList | ForEach-Object { $_.PS.Dispose() }
$pool.Close()Isso testa todos os servidores simultaneamente, reduzindo o tempo total de execução.
Boas práticas
Ao trabalhar com runspaces, é importante seguir algumas boas práticas:
- Limitar o número de runspaces: use
RunspacePoolpara evitar criar threads demais, o que pode causar contenção e degradação de desempenho. - Gerenciar o ciclo de vida: sempre feche e descarte runspaces e instâncias de
PowerShellpara liberar recursos. - Evitar compartilhamento de estado: cada runspace tem seu próprio estado; para compartilhar dados, use variáveis de pool ou objetos sincronizados.
- Tratar exceções: dentro dos scripts executados em runspaces, capture exceções para evitar que o runspace termine abruptamente.
Com essas práticas, você pode aproveitar ao máximo o paralelismo de baixo nível.
Referências
- About Runspaces - Microsoft PowerShell Documentation
- Runspace Class - .NET API
- RunspacePool Class - .NET API
- Creating and Using Runspaces - Scripting Samples
- About Jobs - Microsoft PowerShell Documentation
- Everything About Runspaces - PowerShell Deep Dive
Exercícios
- Crie um runspace simples que execute o comando
Get-Processe exiba a contagem de processos.✓ Resposta:$rs = [runspacefactory]::CreateRunspace() $rs.Open() $ps = [powershell]::Create() $ps.Runspace = $rs $ps.AddScript({ (Get-Process).Count }) $result = $ps.Invoke() $result $ps.Dispose() $rs.Close() - Explique a diferença entre runspaces e jobs em termos de isolamento e performance.✓ Resposta: Runspaces executam em threads dentro do mesmo processo, oferecendo maior performance e menor overhead, mas com isolamento parcial. Jobs executam em processos separados, fornecendo isolamento total, porém com custo de performance devido à criação de processos e serialização de dados.
- Usando um
RunspacePool, execute 5 scripts que façamStart-Sleep -Milliseconds 500e retornem uma mensagem, com no máximo 2 runspaces ativos. Colete e exiba os resultados.✓ Resposta:$pool = [runspacefactory]::CreateRunspacePool(1, 2) $pool.Open() $psList = 1..5 | ForEach-Object { $ps = [powershell]::Create() $ps.RunspacePool = $pool $ps.AddScript("Start-Sleep -Milliseconds 500; 'Tarefa $_ concluída'") $ps } $handles = $psList | ForEach-Object { $_.BeginInvoke() } $results = foreach ($i in 0..4) { $handles[$i].AsyncWaitHandle.WaitOne() $psList[$i].EndInvoke($handles[$i]) } $results $psList | ForEach-Object { $_.Dispose() } $pool.Close() - Escreva um script que use runspaces para calcular o quadrado de números de 1 a 10 em paralelo e exiba os resultados.✓ Resposta:
$pool = [runspacefactory]::CreateRunspacePool(1, 4) $pool.Open() $psList = 1..10 | ForEach-Object { $n = $_ $ps = [powershell]::Create() $ps.RunspacePool = $pool $ps.AddScript("param(`$n) `$n * `$n").AddArgument($n) $ps } $handles = $psList | ForEach-Object { $_.BeginInvoke() } $results = for ($i = 0; $i -lt 10; $i++) { $handles[$i].AsyncWaitHandle.WaitOne() $psList[$i].EndInvoke($handles[$i]) } $results $psList | ForEach-Object { $_.Dispose() } $pool.Close() - Cite três casos de uso onde runspaces são mais adequados que jobs e explique o porquê.✓ Resposta: 1) Processamento de grandes coleções de dados, pois a performance é crítica e o overhead de processos seria proibitivo. 2) Chamadas HTTP em massa, onde a latência de rede domina e runspaces permitem muitas requisições simultâneas com baixo custo. 3) Scripts de automação que precisam executar várias tarefas independentes rapidamente, como verificação de status de serviços em muitos servidores.