Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Śledzenie postępu w wielu wątkach za pomocą
Począwszy od programu PowerShell 7.0, możliwość pracy w wielu wątkach jednocześnie jest możliwa przy użyciu parametru Parallel w ForEach-Object cmdlet. Monitorowanie postępu tych wątków może być jednak wyzwaniem. Zwykle można monitorować postęp procesu przy użyciu write-progress.
Jednak ponieważ program PowerShell używa oddzielnego obszaru roboczego (runspace) dla każdego wątku podczas korzystania z Parallel, przekazywanie postępu z powrotem do hosta nie jest tak bezpośrednie, jak normalne użycie Write-Progress.
Śledzenie postępu za pomocą zsynchronizowanej tabeli skrótów
Podczas pisania postępu z wielu wątków śledzenie staje się trudne, ponieważ podczas uruchamiania równoległych procesów w programie PowerShell każdy proces ma własną przestrzeń uruchomieniową. Aby obejść ten proces, możesz użyć zsynchronizowanej tabeli skrótów. Zsynchronizowana tabela skrótów to struktura danych bezpieczna dla wątków, którą można modyfikować jednocześnie przez wiele wątków bez zgłaszania błędu.
Ustawienia
Jedną z wad tego podejścia jest to, że potrzeba nieco złożonej konfiguracji, aby upewnić się, że wszystko działa bez błędów.
$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)
Ta sekcja tworzy trzy różne struktury danych na potrzeby trzech różnych celów.
Zmienna $dataSet przechowuje tablicę tabel skrótów, która służy do koordynowania kolejnych kroków, bez ryzyka ich zmiany. Jeśli kolekcja obiektów zostanie zmodyfikowana podczas iteracji w kolekcji, program PowerShell zgłasza błąd. Należy zachować kolekcję obiektów w pętli oddzieloną od modyfikowanych obiektów. Klucz Id w każdej tabeli skrótu jest identyfikatorem pozornego procesu. Klucz Wait symuluje obciążenie każdego śledzonego procesu symulowanego.
Zmienna $origin przechowuje zagnieżdżoną tabelę mieszającą, gdzie każdy klucz to jeden z identyfikatorów testowych procesu.
Następnie służy do nawilżania zsynchronizowanej tabeli skrótowej przechowywanej w zmiennej $sync. Zmienna $sync jest odpowiedzialna za raportowanie postępu z powrotem do przestrzeni uruchomieniowej nadrzędnej, która wyświetla postęp.
Uruchamianie procesów
Ta sekcja uruchamia wielowątkowy proces i tworzy niektóre dane wyjściowe używane do wyświetlania postępu.
$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
}
Symulowane procesy są wysyłane do ForEach-Object i uruchamiane jako zadania.
ThrottleLimit jest ustawiony na 3, aby podkreślić uruchamianie wielu procesów w kolejce. Zadania są przechowywane w zmiennej $job i umożliwiają nam poznanie, kiedy wszystkie procesy zakończyły się później.
Podczas używania instrukcji Using: do odwoływania się do zmiennej zakresu nadrzędnego w PowerShell nie można używać wyrażeń w celu dynamicznego jej określania. Jeśli na przykład podjęto próbę utworzenia zmiennej $process w następujący sposób, $process = $Using:sync.$($PSItem.Id)zostanie wyświetlony błąd z informacją, że nie można tam używać wyrażeń. Dlatego tworzymy zmienną $syncCopy, aby móc odwoływać się do zmiennej $sync i modyfikować ją bez ryzyka wystąpienia błędu.
Następnie rozbudujemy tabelę skrótów, aby reprezentowała postęp procesu aktualnie w pętli, używając zmiennej $process i odwołując się do zsynchronizowanych kluczy tabeli skrótów.
Activity i Status są używane jako wartości parametrów dla Write-Progress do wyświetlania statusu danego symulowanego procesu w następnej sekcji.
Pętla foreach to tylko sposób na symulację działania procesu i jest losowana na podstawie atrybutu $dataSetWait do ustawienia Start-Sleep przy użyciu milisekund. Sposób obliczania postępu procesu może się różnić.
Wyświetlanie postępu wielu procesów
Teraz, gdy pozorne procesy działają jako zadania, możemy rozpocząć zapisywanie postępu procesów w oknie programu 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
}
Zmienna $job zawiera zadanie nadrzędne i ma podrzędne zadanie dla każdego z procesów pozornych. Podczas gdy którekolwiek z zadań podrzędnych jest nadal uruchomione, zadanie nadrzędne State pozostanie "Uruchomione". Dzięki temu możemy używać pętli while, aby stale aktualizować postęp każdego procesu do momentu zakończenia wszystkich procesów.
W pętli while przechodzimy przez poszczególne klucze w zmiennej $sync. Ponieważ jest to zsynchronizowana tabela skrótów, jest stale aktualizowana, ale nadal można z niej korzystać bez wystąpienia błędów.
Istnieje kontrola, aby upewnić się, że zgłaszany proces jest rzeczywiście uruchomiony przy użyciu metody IsNullOrEmpty(). Jeśli proces nie został uruchomiony, pętla nie będzie tego raportować i nie przejdzie do kolejnego procesu, dopóki nie trafi na proces, który został uruchomiony. Jeśli proces zostanie uruchomiony, tabela skrótu z bieżącego klucza zostanie użyta do przekazania parametrów do Write-Progress.
Pełny przykład
# 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
}