Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
A partire da PowerShell 7.0, è possibile lavorare contemporaneamente su più thread usando il parametro Parallel nel cmdlet Foreach-Object. Il monitoraggio dello stato di avanzamento di questi thread può essere comunque complicato. In genere, è possibile monitorare lo stato di avanzamento di un processo usando Write-Progress.
Tuttavia, poiché PowerShell usa un spazio di esecuzione separato per ogni thread quando si usa Parallel, la segnalazione dello stato di avanzamento all'host non è altrettanto semplice come l'uso normale di Write-Progress
.
Uso di una tabella hash sincronizzata per monitorare lo stato di avanzamento
Quando si scrive lo stato di avanzamento da più thread, il monitoraggio diventa difficile perché durante l'esecuzione di processi paralleli in PowerShell ogni processo ha uno spazio di esecuzione proprio. Per aggirare questo problema, è possibile usare una tabella hash sincronizzata. Una tabella hash sincronizzata è una struttura di dati thread-safe che può essere modificata da più thread contemporaneamente senza generare un errore.
Impostazione
Uno degli svantaggi di questo approccio è che richiede una configurazione piuttosto complessa per garantire che l'esecuzione avvenga senza errori.
$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)
Questa sezione descrive come creare tre diverse strutture di dati per tre scopi diversi.
La variabile $dataSet
archivia una matrice di tabelle hash usata per coordinare i passaggi successivi senza il rischio che i dati vengano modificati. Se una raccolta di oggetti viene modificata durante l'iterazione della raccolta, PowerShell genera un errore. È necessario mantenere la raccolta di oggetti nel ciclo separata dagli oggetti da modificare. La chiave Id
in ogni tabella hash è l'identificatore per un processo fittizio. La chiave di Wait
simula il carico di lavoro di ogni processo fittizio monitorato.
La variabile $origin
archivia una tabella hash annidata in cui ogni chiave è uno degli ID del processo fittizio.
Viene quindi usata per idratare la tabella hash sincronizzata archiviata nella variabile $sync
. La variabile $sync
è responsabile della segnalazione dello stato di avanzamento allo spazio di esecuzione padre, che lo visualizza.
Esecuzione dei processi
Questa sezione esegue i processi multithread e crea parte dell'output usato per visualizzare lo stato di avanzamento.
$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
}
I processi fittizi vengono inviati a Foreach-Object
e avviati come processi. ThrottleLimit è impostato su 3 per evidenziare l'esecuzione di più processi in una coda. I processi vengono archiviati nella variabile $job
e consentono di stabilire quando tutti i processi sono stati completati in un secondo momento.
Quando si usa l'istruzione using:
per fare riferimento a una variabile di ambito padre in PowerShell, non è possibile usare espressioni per renderla dinamica. Ad esempio, se si tentasse di creare la variabile $process
in questo modo $process = $using:sync.$($PSItem.id)
, verrebbe visualizzato un errore per segnalare che non è possibile usare espressioni. La variabile $syncCopy
viene quindi creata per poter fare riferimento e modificare la variabile $sync
senza il rischio che si verifichi un errore.
Viene poi creata una tabella hash per rappresentare lo stato di avanzamento del processo attualmente nel ciclo usando la variabile $process
tramite riferimento alle chiavi della tabella hash sincronizzata. Le chiavi Activity e Status vengono usate come valori di parametro per Write-Progress
per visualizzare lo stato di un determinato processo fittizio nella sezione successiva.
Il ciclo foreach
è semplicemente un modo per simulare il funzionamento del processo e viene eseguito in modo causale in base all'attributo Wait di $dataSet
per impostare Start-Sleep
usando i millisecondi. La modalità di calcolo dello stato di avanzamento del processo può variare.
Visualizzazione dello stato di avanzamento di più processi
Ora che i processi fittizi vengono eseguiti come processi, è possibile iniziare a scrivere lo stato di avanzamento dei processi nella finestra di 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
}
La variabile $job
contiene il processo padre e ha un processo figlio per ognuno dei processi fittizi. Mentre i processi figlio sono ancora in esecuzione, State per il processo padre rimarrà "Running" (In esecuzione). In questo modo è possibile usare il ciclo while
per aggiornare continuamente lo stato di ogni processo fino al termine di tutti i processi.
All'interno del ciclo while si scorre ogni chiave nella variabile $sync
. Essendo sincronizzata, la tabella hash viene costantemente aggiornata, ma è comunque accessibile senza generare errori.
È previsto un controllo per verificare che il processo monitorato sia effettivamente in esecuzione usando il metodo IsNullOrEmpty()
. Se il processo non è stato avviato, il ciclo non segnalerà lo stato e passerà al successivo fino a quando non rileva un processo avviato. Se il processo è avviato, la tabella hash dalla chiave corrente viene usata per passare i parametri a Write-Progress
.
Esempio 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
}