about_Thread_Jobs
Descrição breve
Fornece informações sobre trabalhos baseados em thread do PowerShell. Um trabalho de thread é um tipo de trabalho em segundo plano que executa um comando ou expressão em um thread separado dentro do processo de sessão atual.
Descrição longa
O PowerShell executa simultaneamente comandos e scripts por meio de trabalhos. Há três tipos de trabalho fornecidos pelo PowerShell para dar suporte à simultaneidade.
RemoteJob
- Comandos e scripts são executados em uma sessão remota. Para obter informações, consulte about_Remote_Jobs.BackgroundJob
- Comandos e scripts são executados em um processo separado na máquina local. Para obter mais informações, consulte about_Jobs.PSTaskJob
ouThreadJob
- Comandos e scripts são executados em um thread separado dentro do mesmo processo no computador local.
Os trabalhos baseados em thread não são tão robustos quanto os trabalhos remotos e em segundo plano, pois são executados no mesmo processo em threads diferentes. Se um trabalho tiver um erro crítico que trave o processo, todos os outros trabalhos no processo serão encerrados.
No entanto, os trabalhos baseados em thread exigem menos sobrecarga. Eles não usam a camada remota ou a serialização. Os objetos resultantes são retornados como referências a objetos ativos na sessão atual. Sem essa sobrecarga, os trabalhos baseados em thread são executados mais rapidamente e usam menos recursos do que os outros tipos de trabalho.
Importante
A sessão pai que criou o trabalho também monitora o status do trabalho e coleta dados do pipeline. O processo filho do trabalho é encerrado pelo processo pai quando o trabalho atinge um estado concluído. Se a sessão pai for encerrada, todos os trabalhos filho em execução serão encerrados junto com seus processos filho.
Existem duas maneiras de contornar essa situação:
- Use
Invoke-Command
para criar trabalhos que são executados em sessões desconectadas. Para obter mais informações, consulte about_Remote_Jobs. - Use
Start-Process
para criar um novo processo em vez de um trabalho. Para saber mais, confira Start-Process.
Como iniciar e gerenciar trabalhos baseados em thread
Há duas maneiras de iniciar trabalhos baseados em thread:
Start-ThreadJob
- do módulo ThreadJobForEach-Object -Parallel -AsJob
- o recurso paralelo foi adicionado no PowerShell 7.0
Use os mesmos cmdlets de trabalho descritos no about_Jobs para gerenciar trabalhos baseados em thread.
Usando Start-ThreadJob
O módulo ThreadJob foi fornecido pela primeira vez com o PowerShell 6. Ele também pode ser instalado na Galeria do PowerShell para Windows PowerShell 5.1.
Para iniciar um trabalho de thread no computador local, use o Start-ThreadJob
cmdlet com um comando ou script entre chaves ({ }
).
O exemplo a seguir inicia um trabalho de thread que executa um Get-Process
comando no computador local.
Start-ThreadJob -ScriptBlock { Get-Process }
O Start-ThreadJob
comando retorna um ThreadJob
objeto que representa o trabalho em execução. O objeto de trabalho contém informações úteis sobre o trabalho, incluindo seu status atual de execução. Ele coleta os resultados do trabalho à medida que os resultados estão sendo gerados.
Usando ForEach-Object -Parallel -AsJob
O PowerShell 7.0 adicionou um novo conjunto de parâmetros ao ForEach-Object
cmdlet. Os novos parâmetros permitem que você execute blocos de script em threads paralelos como trabalhos do PowerShell.
Você pode canalizar dados para ForEach-Object -Parallel
. Os dados são passados para o bloco de script que é executado em paralelo. O -AsJob
parâmetro cria objetos jobs para cada um dos threads paralelos.
O comando a seguir inicia um trabalho que contém trabalhos filho para cada valor de entrada canalizado para o comando. Cada trabalho filho executa o Write-Output
comando com um valor de entrada canalizado como argumento.
1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob
O ForEach-Object -Parallel
comando retorna um PSTaskJob
objeto que contém trabalhos filho para cada valor de entrada canalizado. O objeto de trabalho contém informações úteis sobre o status de execução dos trabalhos filho. Ele coleta os resultados dos trabalhos filho à medida que os resultados estão sendo gerados.
Como aguardar a conclusão de um trabalho e recuperar os resultados do trabalho
Você pode usar cmdlets de trabalho do PowerShell, como Wait-Job
e Receive-Job
para aguardar a conclusão de um trabalho e, em seguida, retornar todos os resultados gerados pelo trabalho.
O comando a seguir inicia um trabalho de thread que executa um Get-Process
comando, aguarda a conclusão do comando e, por fim, retorna todos os resultados de dados gerados pelo comando.
Start-ThreadJob -ScriptBlock { Get-Process } | Wait-Job | Receive-Job
O comando a seguir inicia um trabalho que executa um Write-Output
comando para cada entrada canalizada, aguarda a conclusão de todos os trabalhos filho e, por fim, retorna todos os resultados de dados gerados pelos trabalhos filho.
1..5 | ForEach-Object -Parallel { Write-Output $_ } -AsJob | Wait-Job | Receive-Job
O Receive-Job
cmdlet retorna os resultados dos trabalhos filho.
1
3
2
4
5
Como cada trabalho filho é executado paralelamente, a ordem dos resultados gerados não é garantida.
Desempenho do trabalho de thread
Os trabalhos de thread são mais rápidos e mais leves do que outros tipos de trabalhos. Mas eles ainda têm despesas gerais que podem ser grandes quando comparadas ao trabalho que o trabalho está fazendo.
O PowerShell executa comandos e scripts em uma sessão. Apenas um comando ou script pode ser executado por vez em uma sessão. Portanto, ao executar vários trabalhos, cada trabalho é executado em uma sessão separada. Cada sessão contribui para a sobrecarga.
Os trabalhos de thread fornecem o melhor desempenho quando o trabalho que eles executam é maior do que a sobrecarga da sessão usada para executar o trabalho. Existem dois casos que atendem a esse critério.
O trabalho é intensivo em computação – a execução de um script em vários trabalhos de thread pode aproveitar vários núcleos de processador e concluir mais rapidamente.
O trabalho consiste em espera significativa – um script que gasta tempo aguardando resultados de E/S ou chamadas remotas. A execução em paralelo geralmente é concluída mais rapidamente do que se executada sequencialmente.
(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
O primeiro exemplo acima mostra um loop foreach que cria 1000 trabalhos de thread para fazer uma gravação de cadeia de caracteres simples. Devido à sobrecarga do trabalho, leva mais de 36 segundos para ser concluído.
O segundo exemplo executa o ForEach
cmdlet para fazer as mesmas 1000 operações.
Desta vez, ForEach-Object
é executado sequencialmente, em um único thread, sem nenhuma sobrecarga de trabalho. Ele é concluído em apenas 7 milissegundos.
No exemplo a seguir, até 5000 entradas são coletadas para 10 logs de sistema separados. Como o script envolve a leitura de vários logs, faz sentido fazer as operações em 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
O script é concluído na metade do tempo quando os trabalhos são executados em 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
Trabalhos e variáveis de thread
Há várias maneiras de passar valores para os trabalhos baseados em thread.
Start-ThreadJob
pode aceitar variáveis que são canalizadas para o cmdlet, passadas para o bloco de script por meio da $using
palavra-chave ou passadas por meio do 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
aceita variáveis canalizadas e variáveis passadas diretamente para o bloco de script por meio da $using
palavra-chave.
$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
Como os trabalhos de thread são executados no mesmo processo, qualquer tipo de referência de variável passado para o trabalho deve ser tratado com cuidado. Se não for um objeto thread-safe, ele nunca deverá ser atribuído e o método e as propriedades nunca deverão ser invocados nele.
O exemplo a seguir passa um objeto .NET ConcurrentDictionary
thread-safe para todos os trabalhos filho para coletar objetos de processo nomeados exclusivamente. Como é um objeto thread-safe, ele pode ser usado com segurança enquanto os trabalhos são executados simultaneamente no processo.
$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