Gravando o progresso em vários threads com Foreach Parallel
A partir do PowerShell 7.0, a capacidade de trabalhar em vários threads simultaneamente é possível usando o parâmetro Parallel no cmdlet Foreach-Object . No entanto, monitorar o progresso desses threads pode ser um desafio. Normalmente, você pode monitorar o progresso de um processo usando Write-Progress.
No entanto, como o PowerShell usa um espaço de execução separado para cada thread ao usar o Parallel, relatar o progresso para o host não é tão simples quanto o uso normal do Write-Progress
.
Usando uma hashtable sincronizada para acompanhar o progresso
Ao escrever o progresso de vários threads, o acompanhamento torna-se difícil porque, ao executar processos paralelos no PowerShell, cada processo tem seu próprio espaço de execução. Para contornar isso, você pode usar uma hashtable sincronizada. Uma hashtable sincronizada é uma estrutura de dados segura de thread que pode ser modificada por vários threads simultaneamente sem gerar um erro.
Configurar
Uma das desvantagens dessa abordagem é que ela precisa de uma configuração um pouco complexa para garantir que tudo seja executado sem erros.
$dataset = @(
@{
Id = 1
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 2
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 3
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 4
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 5
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
)
# Create a hashtable for process.
# Keys should be ID's of the processes
$origin = @{}
$dataset | Foreach-Object {$origin.($_.id) = @{}}
# Create synced hashtable
$sync = [System.Collections.Hashtable]::Synchronized($origin)
Esta seção cria três estruturas de dados diferentes, para três finalidades diferentes.
A $dataSet
variável armazena uma matriz de hashtables que é usada para coordenar as próximas etapas sem o risco de ser modificada. Se uma coleção de objetos for modificada durante a iteração pela coleção, o PowerShell lançará um erro. Você deve manter a coleção de objetos no loop separada dos objetos que estão sendo modificados. A Id
chave em cada hashtable é o identificador de um processo simulado. A Wait
chave simula a carga de trabalho de cada processo simulado que está sendo rastreado.
A $origin
variável armazena uma hashtable aninhada com cada chave sendo uma das id's do processo fictício.
Em seguida, ele é usado para hidratar a hashtable sincronizada armazenada na $sync
variável. A $sync
variável é responsável por relatar o progresso de volta para o espaço de execução pai, que exibe o progresso.
Executando os processos
Esta seção executa os processos multi-threaded e cria algumas das saídas usadas para exibir o progresso.
$job = $dataset | Foreach-Object -ThrottleLimit 3 -AsJob -Parallel {
$syncCopy = $using:sync
$process = $syncCopy.$($PSItem.Id)
$process.Id = $PSItem.Id
$process.Activity = "Id $($PSItem.Id) starting"
$process.Status = "Processing"
# Fake workload start up that takes x amount of time to complete
start-sleep -Milliseconds ($PSItem.wait*5)
# Process. update activity
$process.Activity = "Id $($PSItem.id) processing"
foreach ($percent in 1..100)
{
# Update process on status
$process.Status = "Handling $percent/100"
$process.PercentComplete = (($percent / 100) * 100)
# Fake workload that takes x amount of time to complete
Start-Sleep -Milliseconds $PSItem.Wait
}
# Mark process as completed
$process.Completed = $true
}
Os processos simulados são enviados Foreach-Object
e iniciados como trabalhos. O ThrottleLimit é definido como 3 para realçar a execução de vários processos em uma fila. Os trabalhos são armazenados na $job
variável e nos permite saber quando todos os processos terminaram mais tarde.
Ao usar a using:
instrução para fazer referência a uma variável de escopo pai no PowerShell, você não pode usar expressões para torná-la dinâmica. Por exemplo, se você tentasse criar a $process
variável assim, $process = $using:sync.$($PSItem.id)
obteria um erro informando que não pode usar expressões lá. Assim, criamos a $syncCopy
variável para poder referenciar e modificar a $sync
variável sem o risco de ela falhar.
Em seguida, construímos uma hashtable para representar o progresso do processo atualmente no loop usando a $process
variável fazendo referência às chaves de hashtable sincronizadas. As chaves Activity e Statussão usadas como valores de parâmetro para Write-Progress
exibir o status de um determinado processo simulado na próxima seção.
O foreach
loop é apenas uma maneira de simular o processo funcionando e é randomizado com base no $dataSet
atributo Wait para definir Start-Sleep
usando milissegundos. A forma como calcula o progresso do seu processo pode variar.
Exibindo o progresso de vários processos
Agora que os processos fictícios estão sendo executados como trabalhos, podemos começar a gravar o progresso dos processos na janela do PowerShell.
while($job.State -eq 'Running')
{
$sync.Keys | Foreach-Object {
# If key is not defined, ignore
if(![string]::IsNullOrEmpty($sync.$_.keys))
{
# Create parameter hashtable to splat
$param = $sync.$_
# Execute Write-Progress
Write-Progress @param
}
}
# Wait to refresh to not overload gui
Start-Sleep -Seconds 0.1
}
A $job
variável contém o trabalho pai e tem um trabalho filho para cada um dos processos simulados. Enquanto qualquer um dos trabalhos filho ainda estiver em execução, o Estado do trabalho pai permanecerá "Em execução". Isso nos permite usar o while
loop para atualizar continuamente o progresso de cada processo até que todos os processos sejam concluídos.
Dentro do loop while, fazemos um loop através de cada uma das chaves na $sync
variável. Uma vez que este é um hashtable sincronizado, é constantemente atualizado, mas ainda pode ser acessado sem lançar erros.
Há uma verificação para garantir que o processo que está sendo relatado está realmente sendo executado usando o IsNullOrEmpty()
método. Se o processo não tiver sido iniciado, o loop não relatará e passará para o próximo até chegar a um processo que foi iniciado. Se o processo for iniciado, o hashtable da chave atual será usado para splat os parâmetros para Write-Progress
.
Exemplo completo
# Example workload
$dataset = @(
@{
Id = 1
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 2
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 3
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 4
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
@{
Id = 5
Wait = 3..10 | get-random | Foreach-Object {$_*100}
}
)
# Create a hashtable for process.
# Keys should be ID's of the processes
$origin = @{}
$dataset | Foreach-Object {$origin.($_.id) = @{}}
# Create synced hashtable
$sync = [System.Collections.Hashtable]::Synchronized($origin)
$job = $dataset | Foreach-Object -ThrottleLimit 3 -AsJob -Parallel {
$syncCopy = $using:sync
$process = $syncCopy.$($PSItem.Id)
$process.Id = $PSItem.Id
$process.Activity = "Id $($PSItem.Id) starting"
$process.Status = "Processing"
# Fake workload start up that takes x amount of time to complete
start-sleep -Milliseconds ($PSItem.wait*5)
# Process. update activity
$process.Activity = "Id $($PSItem.id) processing"
foreach ($percent in 1..100)
{
# Update process on status
$process.Status = "Handling $percent/100"
$process.PercentComplete = (($percent / 100) * 100)
# Fake workload that takes x amount of time to complete
Start-Sleep -Milliseconds $PSItem.Wait
}
# Mark process as completed
$process.Completed = $true
}
while($job.State -eq 'Running')
{
$sync.Keys | Foreach-Object {
# If key is not defined, ignore
if(![string]::IsNullOrEmpty($sync.$_.keys))
{
# Create parameter hashtable to splat
$param = $sync.$_
# Execute Write-Progress
Write-Progress @param
}
}
# Wait to refresh to not overload gui
Start-Sleep -Seconds 0.1
}
Ligações Relacionadas
Comentários
https://aka.ms/ContentUserFeedback.
Brevemente: Ao longo de 2024, vamos descontinuar progressivamente o GitHub Issues como mecanismo de feedback para conteúdos e substituí-lo por um novo sistema de feedback. Para obter mais informações, veja:Submeter e ver comentários