A un certo punto, la maggior parte delle persone si troverà di fronte al problema che uno script di base di PowerShell è troppo lento per risolvere. Questo potrebbe riguardare la raccolta di dati da molti computer sulla rete o forse la creazione di un gran numero di nuovi utenti nell’Active Directory contemporaneamente. Questi sono entrambi ottimi esempi di situazioni in cui l’utilizzo di una maggiore potenza di elaborazione consentirebbe al codice di eseguirsi più velocemente. Vediamo come risolvere questo problema utilizzando il multithreading di PowerShell!
La sessione predefinita di PowerShell è single-threaded. Esegue un comando alla volta e, quando termina, passa al comando successivo. Questo è utile perché rende tutto ripetibile e non utilizza molte risorse. Ma cosa succede se le azioni che sta eseguendo non dipendono l’una dall’altra e si dispone delle risorse della CPU da dedicare? In tal caso, è il momento di iniziare a pensare al multithreading.
In questo articolo, imparerai come comprendere e utilizzare diverse tecniche di multithreading di PowerShell per elaborare contemporaneamente più flussi di dati, gestiti dalla stessa console.
Comprensione del multithreading di PowerShell
Il multithreading è un modo per eseguire più di un comando contemporaneamente. Mentre PowerShell normalmente utilizza un singolo thread, ci sono molti modi per utilizzarne più di uno per parallelizzare il codice.
Il principale vantaggio del multithreading è ridurre il tempo di esecuzione del codice. Questa riduzione del tempo comporta un maggiore requisito di potenza di elaborazione. Quando si utilizza il multithreading, vengono eseguite molte azioni contemporaneamente, richiedendo quindi più risorse di sistema.
Ad esempio, cosa succederebbe se volessi creare un nuovo utente in Active Directory? In questo esempio, non c’è nulla da multithread perché viene eseguito solo un comando. Tutto questo cambia quando si desidera creare 1000 nuovi utenti.
Senza multithreading, eseguiresti il comando New-ADUser
1000 volte per creare tutti gli utenti. Forse ci vogliono tre secondi per creare un nuovo utente. Per creare tutti i 1000 utenti, ci vorrebbe poco meno di un’ora. Invece di utilizzare un solo thread per 1000 comandi, potresti invece utilizzare 100 thread, ognuno eseguendo dieci comandi ciascuno. Ora, invece di impiegare circa 50 minuti, ci vorrà meno di un minuto!
Si noti che non si otterrà una scalabilità perfetta. L’azione di avviare e interrompere gli elementi nel codice richiederà del tempo. Utilizzando un singolo thread, PowerShell deve eseguire il codice ed è fatto. Con thread multipli, il thread originale utilizzato per eseguire la console verrà utilizzato per gestire gli altri thread. Ad un certo punto, il thread originale sarà saturato nel mantenere tutti gli altri thread in linea.
Prerequisiti per il multithreading di PowerShell
In questo articolo imparerai come funziona il multithreading di PowerShell in modo pratico. Se vuoi seguire insieme, di seguito trovi alcune cose di cui avrai bisogno e alcuni dettagli sull’ambiente utilizzato.
- Windows PowerShell versione 3 o superiore – A meno che non sia esplicitamente dichiarato, tutto il codice dimostrato funzionerà in Windows PowerShell versione 3 o superiore. Per gli esempi verrà utilizzata la versione 5.1 di Windows PowerShell.
- CPU e memoria di riserva – Avrai bisogno di almeno un po’ di CPU e memoria extra per parallelizzare con PowerShell. Se non hai queste risorse disponibili, potresti non vedere alcun beneficio in termini di prestazioni.
Priorità #1: Correggi il tuo codice!
Prima di tuffarti nell’accelerare i tuoi script con il multithreading di PowerShell, ci sono alcuni pezzi di lavoro preparatorio che vorrai completare. Il primo è ottimizzare il tuo codice.
Sebbene tu possa lanciare più risorse al tuo codice per farlo funzionare più velocemente, il multithreading porta molta complessità extra. Se ci sono modi per velocizzare il tuo codice prima del multithreading, dovrebbero essere fatti prima.
Identifica i Colli di Bottiglia
Uno dei primi passi nel parallelizzare il tuo codice è scoprire cosa lo sta rallentando. Il codice potrebbe essere lento a causa di una logica sbagliata o di loop extra dove puoi fare alcune modifiche per permettere un’esecuzione più veloce prima del multithreading.
Un esempio di un modo comune per velocizzare il tuo codice è spostare il tuo filtraggio a sinistra. Se stai interagendo con un mucchio di dati, qualsiasi filtraggio che vuoi permettere per ridurre la quantità di dati dovrebbe essere fatto il prima possibile. Di seguito è riportato un esempio di codice per ottenere la quantità di CPU utilizzata dal processo svchost.
L’esempio seguente legge tutti i processi in esecuzione, quindi filtra un singolo processo (svchost). Seleziona quindi la proprietà CPU e verifica che il valore non sia nullo.
Confronta il codice sopra con l’esempio seguente. Di seguito c’è un altro esempio di codice che ha lo stesso output ma è organizzato in modo diverso. Nota che il codice sottostante è più semplice e sposta tutta la logica possibile a sinistra del simbolo di pipe. Ciò impedisce a Get-Process
di restituire processi non rilevanti.
Di seguito è riportata la differenza di tempo per l’esecuzione delle due righe sopra. Se la differenza di 117 ms non sarà percettibile se esegui questo codice solo una volta, inizierà ad accumularsi se eseguito migliaia di volte.

Utilizzo di un codice thread-safe
Inoltre, assicurati che il tuo codice sia “thread-safe”. Il termine “thread-safe” si riferisce al fatto che se un thread sta eseguendo del codice, un altro thread può eseguire lo stesso codice contemporaneamente senza causare un conflitto.
Ad esempio, scrivere sullo stesso file in due thread diversi non è thread-safe poiché non si sa cosa aggiungere al file per primo. Mentre due thread che leggono da un file sono thread-safe poiché il file non viene modificato. Entrambi i thread ottengono lo stesso output.
Il problema con il codice di multithreading di PowerShell che non è thread-safe è che si possono ottenere risultati inconsistenti. A volte può funzionare correttamente perché i thread si avviano nel momento giusto per evitare conflitti. Altre volte, si avrà un conflitto e la risoluzione del problema sarà difficile a causa degli errori inconsistenti.
Se stai eseguendo solo due o tre lavori alla volta, potrebbe capitare che si allineino perfettamente in modo che tutti scrivano sul file in momenti diversi. Quando però aumenti il codice a 20 o 30 lavori, la probabilità che almeno due di essi provino a scrivere contemporaneamente diminuisce notevolmente.
Esecuzione parallela con PSJobs
Uno dei modi più semplici per multithreading di uno script è utilizzando PSJobs. I PSJobs hanno cmdlet incorporati nel modulo Microsoft.PowerShell.Core. Il modulo Microsoft.PowerShell.Core è incluso in tutte le versioni di PowerShell dalla versione 3. I comandi in questo modulo ti consentono di eseguire il codice in background mentre continui a eseguire un codice diverso in foreground. Di seguito troverai tutti i comandi disponibili.

Tenere traccia dei tuoi lavori
Tutti i PSJobs si trovano in uno dei undici stati. Questi stati sono come PowerShell gestisce i lavori.
Di seguito troverai un elenco degli stati più comuni in cui può trovarsi un lavoro.
- Completato – Il lavoro è terminato e i dati di output possono essere recuperati o il lavoro può essere rimosso.
- In esecuzione – Il lavoro è attualmente in esecuzione e non può essere rimosso senza interrompere forzatamente il lavoro. L’output non può essere ancora recuperato.
- Bloccato – Il lavoro è ancora in esecuzione, ma l’host sta attendendo un prompt di informazioni prima di poter procedere.
- Non riuscito – Si è verificato un errore di terminazione durante l’esecuzione del lavoro.
Per ottenere lo stato di un lavoro avviato, utilizzare il comando Get-Job
. Questo comando restituisce tutti gli attributi dei lavori.
Di seguito è riportato l’output per un lavoro in cui è possibile vedere che lo stato è Completato. Nell’esempio seguente viene eseguito il codice Start-Sleep 5
all’interno di un lavoro utilizzando il comando Start-Job
. Lo stato di tale lavoro viene quindi restituito utilizzando il comando Get-Job
.

Quando lo stato del lavoro restituisce Completato, ciò significa che il codice nello scriptblock è stato eseguito ed è terminato. È anche possibile vedere che la proprietà HasMoreData
è False
. Ciò significa che non c’era alcun output da fornire dopo la conclusione del lavoro.
Di seguito è riportato un esempio di alcuni degli altri stati utilizzati per descrivere i lavori. Si può vedere dalla colonna Command
che ciò che potrebbe aver causato l’interruzione di alcuni di questi lavori, come ad esempio tentare di aspettare per abc
secondi, ha portato a un lavoro non riuscito.

Creazione di nuovi lavori
Come si è visto in precedenza, il comando Start-Job
consente di creare un nuovo lavoro che avvia l’esecuzione del codice nel lavoro. Quando si crea un lavoro, si fornisce uno scriptblock che viene utilizzato per il lavoro. Il PSJob quindi crea un lavoro con un numero di ID univoco e inizia l’esecuzione del lavoro.
Il principale vantaggio qui è che il comando Start-Job
impiega meno tempo rispetto all’esecuzione dello scriptblock che stiamo utilizzando. Come si può vedere nell’immagine sottostante, anziché il comando impiegare cinque secondi per essere completato, sono bastati solo 0,15 secondi per avviare il job.

Il motivo per cui è stato possibile eseguire lo stesso codice in una frazione di tempo è perché è stato eseguito in background come PSJob. Sono bastati 0,15 secondi per configurare ed avviare l’esecuzione del codice in background, invece di eseguirlo in foreground e attendere effettivamente per cinque secondi.
Recupero dell’output del job
A volte il codice all’interno del job restituisce un output. È possibile recuperare l’output di quel codice utilizzando il comando Receive-Job
. Il comando Receive-Job
accetta un PSJob come input e quindi scrive l’output del job sulla console. Tutto ciò che è stato prodotto in output dal job durante la sua esecuzione è stato memorizzato, quindi quando il job viene recuperato, viene visualizzato tutto ciò che era stato memorizzato in quel momento.
Un esempio di ciò è l’esecuzione del codice di seguito riportato. Questo creerà e avvierà un job che scriverà Hello World in output. Successivamente, verrà recuperato l’output del job e verrà visualizzato sulla console.

Creazione di job pianificati
Un altro modo per interagire con PSJobs è tramite un lavoro pianificato. I lavori pianificati sono simili a un’attività pianificata di Windows che può essere configurata con Task Scheduler. I lavori pianificati creano un modo per pianificare facilmente scriptblock di PowerShell complessi in un’attività pianificata. Utilizzando un lavoro pianificato, è possibile eseguire un PSJob in background in base a trigger.
Trigger dei lavori
I trigger dei lavori possono essere cose come un orario specifico, quando un utente effettua il login, quando il sistema si avvia e molti altri. È anche possibile fare in modo che i trigger si ripetano a intervalli regolari. Tutti questi trigger vengono definiti con il comando New-JobTrigger
. Questo comando viene utilizzato per specificare un trigger che eseguirà il lavoro pianificato. Un lavoro pianificato senza trigger deve essere eseguito manualmente, ma ogni lavoro può avere molti trigger.
Oltre ad avere un trigger, avrai comunque un blocco di script come quello utilizzato con un normale PSJob. Una volta ottenuti sia il trigger che il blocco di script, userai il comando Register-ScheduledJob
per creare il lavoro come mostrato nella sezione successiva. Questo comando viene utilizzato per specificare attributi del lavoro pianificato come lo scriptblock che verrà eseguito e i trigger creati con il comando New-JobTrigger
.
Esempio
Forse hai bisogno di eseguire del codice PowerShell ogni volta che qualcuno effettua il login su un computer. Puoi creare un lavoro pianificato per questo.
Per fare ciò, dovresti prima definire un trigger utilizzando New-JobTrigger
e definire il lavoro pianificato come mostrato di seguito. Questo lavoro pianificato scriverà una riga in un file di log ogni volta che qualcuno effettua l’accesso.
Dopo aver eseguito i comandi sopra riportati, otterrai un output simile a quando si crea un nuovo lavoro che mostrerà l’ID del lavoro, il blocco di script e altri attributi come mostrato di seguito.

Dopo alcuni tentativi di accesso, puoi vedere dalla schermata sottostante che sono stati registrati i tentativi.

Sfruttare il parametro AsJob
Un altro modo per utilizzare i lavori è utilizzare il parametro AsJob
che è incorporato in molti comandi di PowerShell. Poiché ci sono molti comandi diversi, è possibile trovarli tutti utilizzando Get-Command
come mostrato di seguito.
Uno dei comandi più diffusi è Invoke-Command
. Normalmente, quando si esegue questo comando, inizierà immediatamente l’esecuzione di un comando. Mentre alcuni comandi restituiranno immediatamente, consentendoti di continuare con ciò che stavi facendo, altri aspetteranno fino a quando il comando non sarà terminato.
Utilizzando il parametro AsJob
si ottiene esattamente ciò che suggerisce e si esegue il comando eseguito come lavoro anziché eseguirlo in modo sincrono nella console.
Sebbene nella maggior parte dei casi AsJob
possa essere utilizzato con il computer locale, Invoke-Command
non ha un’opzione nativa per eseguire il comando sul computer locale. C’è un trucco che consiste nell’utilizzare Localhost come valore del parametro ComputerName
. Di seguito è riportato un esempio di questo trucco.
Per mostrare il parametro AsJob
in azione, l’esempio seguente utilizza Invoke-Command
per attendere cinque secondi e quindi ripetere lo stesso comando utilizzando AsJob
per mostrare la differenza nei tempi di esecuzione.

Runspaces: Un po’ come i job ma più veloce!
Fino ad ora, hai imparato come utilizzare thread aggiuntivi con PowerShell utilizzando solo i comandi incorporati. Un’altra opzione per eseguire script multithread è quella di utilizzare uno spazio di esecuzione separato.
I runspaces sono l’area chiusa in cui operano i thread che eseguono PowerShell. Mentre lo spazio di esecuzione utilizzato dalla console di PowerShell è limitato a un singolo thread, è possibile utilizzare spazi di esecuzione aggiuntivi per consentire l’utilizzo di thread aggiuntivi.
Runspace vs PSJobs
Pur avendo molte somiglianze, c’è una grande differenza in termini di prestazioni tra uno spazio di esecuzione e un PSJob. La differenza principale tra runspaces e PSJobs è il tempo necessario per configurarli e smontarli.
Nell’esempio della sezione precedente, la creazione del PSJob ha richiesto circa 150 ms. Questo è un caso ideale poiché lo scriptblock del job non includeva molto codice e non venivano passate variabili aggiuntive al job.
A differenza della creazione di un PSJob, uno spazio di esecuzione viene creato in anticipo. La maggior parte del tempo necessario per avviare un lavoro in uno spazio di esecuzione viene gestita prima dell’aggiunta di qualsiasi codice.
Ecco un esempio di esecuzione dello stesso comando che abbiamo utilizzato per il PSJob nello spazio di esecuzione.

In contrasto, di seguito è riportato il codice utilizzato per la versione di runspace. È possibile vedere che c’è molto più codice per eseguire la stessa operazione. Ma il vantaggio del codice aggiuntivo riduce di quasi 3/4 il tempo permettendo al comando di iniziare l’esecuzione in 36ms invece di 148ms.

Esecuzione di Runspaces: Una panoramica
L’utilizzo di runspaces può essere intimidatorio all’inizio poiché non c’è più un comando di PowerShell che ti guida. Dovrai trattare direttamente con le classi .NET. In questa sezione, vediamo cosa serve per creare un runspace in PowerShell.
In questa panoramica, creerai un runspace separato dalla console di PowerShell e un’istanza separata di PowerShell. Successivamente assegnerai il nuovo runspace alla nuova istanza di PowerShell e aggiungerai del codice a quell’istanza.
Crea il Runspace
La prima cosa da fare è creare il nuovo runspace. Puoi farlo utilizzando la classe runspacefactory
. Memorizzalo in una variabile, come mostrato di seguito, in modo da poterlo referenziare in seguito.
Ora che il runspace è stato creato, assegnalo a un’istanza di PowerShell per eseguire il codice di PowerShell. Per fare ciò, userai la classe powershell
e, simile al runspace, dovrai memorizzarlo in una variabile come mostrato di seguito.
Successivamente, aggiungi il runspace alla tua istanza di PowerShell, apri il runspace per poter eseguire il codice e aggiungi il tuo scriptblock. Ciò viene mostrato di seguito con uno scriptblock che fa dormire il processo per cinque secondi.
Esegui il Runspace
Finora, lo scriptblock non è ancora stato eseguito. Finora è stato solo definito tutto per il runspace. Per avviare l’esecuzione dello scriptblock, hai due opzioni.
- Invoke() – Il metodo
Invoke()
esegue lo scriptblock nel runspace, ma attende il ritorno alla console fino a quando il runspace non restituisce. Questo è utile per testare che il codice venga eseguito correttamente prima di lasciarlo libero. - BeginInvoke() – Utilizzare il metodo
BeginInvoke()
è quello che desideri per ottenere effettivamente un miglioramento delle prestazioni. Questo avvierà l’esecuzione dello scriptblock nel runspace e ti riporterà immediatamente alla console.
Quando si utilizza BeginInvoke()
, memorizza l’output in una variabile in quanto sarà necessario per vedere lo stato dello scriptblock nel runspace come mostrato di seguito.
Una volta che hai memorizzato l’output del BeginInvoke()
in una variabile, puoi controllare quella variabile per vedere lo stato del lavoro come mostrato di seguito nella proprietà IsCompleted
.

Un’altra ragione per cui è necessario memorizzare l’output in una variabile è che, a differenza del metodo Invoke()
, BeginInvoke()
non restituirà automaticamente l’output quando il codice è terminato. Per fare ciò, è necessario utilizzare il metodo EndInvoke()
una volta completato.
In questo esempio, non ci sarebbe alcun output, ma per terminare l’invoke si utilizzerebbe il comando seguente.
Una volta completate tutte le attività in coda nello spazio di esecuzione, è sempre necessario chiudere lo spazio di esecuzione. Ciò consentirà al processo di pulizia automatica dei rifiuti di PowerShell di eliminare le risorse inutilizzate. Di seguito è riportato il comando che si utilizzerebbe per farlo.
Utilizzo di Runspace Pools
Sebbene l’utilizzo di uno spazio di esecuzione migliori le prestazioni, si incontra un limite importante di un singolo thread. È qui che gli spazi di esecuzione di pool si distinguono per l’utilizzo di thread multipli.
Nella sezione precedente, si stava utilizzando solo due spazi di esecuzione. Si è utilizzato solo uno per la console PowerShell stessa e quello che si era creato manualmente. Gli spazi di esecuzione di pool consentono di avere più spazi di esecuzione gestiti in background utilizzando una singola variabile.
Pur essendo possibile ottenere questo comportamento multi-spazio di esecuzione con più oggetti spazio di esecuzione, l’utilizzo di uno spazio di esecuzione di pool semplifica molto la gestione.
Gli spazi di esecuzione di pool differiscono dagli spazi di esecuzione singoli per quanto riguarda la configurazione. Una delle differenze chiave è che si definisce il numero massimo di thread che possono essere utilizzati per lo spazio di esecuzione del pool. Con un singolo spazio di esecuzione, è limitato a un singolo thread, ma con un pool si specifica il numero massimo di thread a cui il pool può scalare.
Il numero consigliato di thread in uno spazio di esecuzione di pool dipende dal numero di attività da svolgere e dalla macchina in cui viene eseguito il codice. Sebbene aumentare il numero massimo di thread non influisca negativamente sulla velocità nella maggior parte dei casi, potrebbe anche non comportare alcun beneficio.
Dimostrazione di velocità dello spazio di esecuzione del pool.
Per mostrare un esempio in cui un pool di runspace è migliore di un singolo runspace, potresti voler creare dieci nuovi file. Se usassi un singolo runspace per questa operazione, creeresti il primo file, poi passeresti al secondo e poi al terzo, e così via fino a creare tutti e dieci i file. Lo scriptblock per questo esempio potrebbe avere un aspetto simile a quello riportato di seguito. Alimenterei questo scriptblock con dieci nomi di file in un ciclo e verrebbero tutti creati.
Nell’esempio seguente viene definito un blocco di script che contiene uno script breve che accetta un nome e crea un file con quel nome. Viene creato un pool di runspace con un massimo di 5 thread.
Successivamente, un ciclo si ripete dieci volte e ogni volta assegna il numero dell’iterazione a $_
. Quindi avrà 1 nella prima iterazione, 2 nella seconda e così via.
Il ciclo crea un oggetto PowerShell, assegna il blocco di script e l’argomento per lo script e avvia il processo.
Infine, alla fine del ciclo, attende che tutte le attività in coda finiscano.
Ora, anziché creare thread uno alla volta, ne verranno creati cinque alla volta. Senza pool di runspace, dovresti creare e gestire cinque runspaces separati e cinque istanze separate di Powershell
. Questa gestione diventa rapidamente un caos.
Invece, puoi creare un pool di runspace, un’istanza di PowerShell, utilizzare lo stesso blocco di codice e lo stesso ciclo. La differenza è che il runspace si adatterà per usare automaticamente tutti e cinque di quei thread.
Creazione di pool di Runspace
La creazione di un pool di spazi di esecuzione è molto simile alla creazione di uno spazio di esecuzione nella sezione precedente. Di seguito è riportato un esempio su come farlo. L’aggiunta di uno scriptblock e l’invocazione del processo sono identici a uno spazio di esecuzione. Come puoi vedere qui sotto, il pool di spazi di esecuzione viene creato con un massimo di cinque thread.
Confronto tra spazi di esecuzione e pool di spazi di esecuzione per la velocità
Per dimostrare la differenza tra uno spazio di esecuzione e un pool di spazi di esecuzione, crea uno spazio di esecuzione ed esegui il comando Start-Sleep
come fatto in precedenza. Questa volta, però, deve essere eseguito 10 volte. Come puoi vedere nel codice qui sotto, viene creato uno spazio di esecuzione che si metterà in pausa per 5 secondi.
Nota che, essendo utilizzato un singolo spazio di esecuzione, devi attendere che venga completato prima di poterne avviare un altro. Per questo motivo, viene aggiunta una pausa di 100 ms fino al completamento del lavoro. Sebbene questo possa essere ridotto, si avrà una diminuzione del rendimento poiché si impiegherà più tempo per verificare se il lavoro è stato completato che per attendere il completamento del lavoro stesso.
Dall’esempio qui sotto, puoi vedere che ci sono voluti circa 51 secondi per completare 10 insiemi di pause di 5 secondi.

Ora, invece di utilizzare un singolo spazio di esecuzione, passa a un pool di spazi di esecuzione. Di seguito è riportato il codice che verrà eseguito. Puoi vedere che ci sono alcune differenze tra l’uso dei due nel codice qui sotto quando si utilizza un pool di spazi di esecuzione.
Come puoi vedere qui sotto, questo viene completato in poco più di 10 secondi, il che è molto migliore rispetto ai 51 secondi per il singolo spazio di esecuzione.

Di seguito è riportato un riassunto della differenza tra uno spazio di esecuzione e un pool di spazi di esecuzione in questi esempi.
Property | Runspace | Runspace Pool |
---|---|---|
Wait Delay | Waiting for each job to finish before continuing to the next. | Starting all of the jobs and then waiting until they have all finished. |
Amount of Threads | One | Five |
Runtime | 50.8 Seconds | 10.1 Seconds |
Entrare gradualmente in Runspaces con PoshRSJob
A frequent occurrence when programming is that you will do what is more comfortable and accept the small loss in performance. This could be because it makes the code easier to write or easier to read, or it could just be your preference.
La stessa cosa accade con PowerShell, dove alcune persone utilizzano PSJobs invece di runspaces a causa della facilità di utilizzo. Ci sono alcune cose che possono essere fatte per trovare un compromesso e ottenere migliori prestazioni senza renderlo troppo più difficile da usare.
Esiste un modulo ampiamente utilizzato chiamato PoshRSJob che contiene moduli che corrispondono allo stile di normali PSJobs ma con il vantaggio aggiunto di utilizzare runspaces. Invece di dover specificare tutto il codice per creare il runspace e l’oggetto powershell, il modulo PoshRSJob si occupa di tutto ciò quando si eseguono i comandi.
Per installare il modulo, eseguire il comando sottostante in una sessione PowerShell amministrativa.
Una volta installato il modulo, è possibile notare che i comandi sono gli stessi dei comandi PSJob con il prefisso RS. Invece di Start-Job
è Start-RSJob
. Invece di Get-Job
è Get-RSJob
.
Ecco un esempio di come eseguire lo stesso comando in un PSJob e poi ancora in un RSJob. Come si può vedere, hanno una sintassi e un output molto simili, ma non sono del tutto identici.

Di seguito è riportato del codice che può essere utilizzato per confrontare la differenza di velocità tra un PSJob e un RSJob.
Come si può vedere qui sotto, c’è una grande differenza di velocità poiché gli RSJob stanno ancora utilizzando runspaces sotto la copertura.

Foreach-Object -Parallel
La comunità di PowerShell desiderava un modo più semplice e integrato per eseguire processi in multithreading in modo rapido. Lo switch parallelo è ciò che è emerso da questo desiderio.
Al momento in cui scrivo, PowerShell 7 è ancora in anteprima, ma hanno aggiunto un parametro “Parallel” al comando “Foreach-Object”. Questo processo utilizza spazi di lavoro per parallelizzare il codice e utilizza il blocco di script utilizzato per “Foreach-Object” come blocco di script per lo spazio di lavoro.
Sebbene i dettagli siano ancora in fase di definizione, questa potrebbe essere un modo più semplice per utilizzare gli spazi di lavoro in futuro. Come puoi vedere di seguito, puoi eseguire rapidamente cicli su molti set di “sleep”.

Sfide del multithreading
Sebbene il multithreading sembri fantastico, non è proprio così. Ci sono molte sfide che si presentano quando si utilizza il multithreading in qualsiasi codice.
Utilizzo delle variabili
Una delle sfide più grandi e ovvie del multithreading è che non è possibile condividere variabili senza passarle come argomenti. C’è un’eccezione con un Hashtable sincronizzato, ma questo è un argomento per un altro giorno.
I PSJobs e gli spazi di lavoro operano senza alcun accesso alle variabili esistenti e non esiste un modo per interagire con le variabili utilizzate in spazi di lavoro diversi dalla console.
Questo rappresenta una grande sfida per il passaggio dinamico di informazioni a questi lavori. La risposta è diversa a seconda del tipo di multithreading che si utilizza.
Per i comandi Start-Job
e Start-RSJob
del modulo PoshRSJob, puoi utilizzare il parametro ArgumentList
per fornire una lista di oggetti che verranno passati come parametri allo scriptblock nell’ordine in cui li elenchi. Di seguito sono riportati esempi dei comandi utilizzati per i PSJob e i RSJob.
PSJob:
RSJob:
I runspaces nativi non offrono la stessa facilità. Invece, è necessario utilizzare il metodo AddArgument()
sull’oggetto PowerShell. Di seguito è riportato un esempio di come sarebbe per ciascuno.
Runspace:
Anche i pool di runspace funzionano allo stesso modo, di seguito è riportato un esempio di come aggiungere un argomento a un pool di runspace.
Logging
Il multithreading introduce anche sfide di logging. Poiché ogni thread opera in modo indipendente dagli altri, non possono tutti registrare in un unico punto. Se si tentasse di registrare su un file con più thread, quando un thread scriveva sul file, gli altri thread non potevano farlo. Questo potrebbe rallentare il codice o causarne il fallimento completo.
A titolo di esempio, di seguito è riportato del codice che tenta di registrare 100 volte su un singolo file utilizzando 5 thread in un pool di runspace.
Dall’output non vedrai errori, ma se guardi la dimensione del file di testo, puoi vedere di seguito che non tutti i 100 lavori sono stati completati correttamente.

Alcuni modi per aggirare il problema consistono nel registrare su file separati. Ciò elimina il problema del blocco del file, ma comporta la presenza di molti file di log che dovrai analizzare per capire tutto ciò che è successo.
Un’altra alternativa è permettere che il timing di alcune delle uscite sia sbagliato e che venga registrato solo ciò che un lavoro ha fatto una volta terminato. Ciò ti permette di avere tutto serializzato attraverso la tua sessione originale, ma perdi alcuni dettagli perché non sai necessariamente in quale ordine si è verificato tutto.
Riepilogo
Anche se il multithreading può fornire enormi vantaggi in termini di prestazioni, può anche causare problemi. Mentre alcuni carichi di lavoro ne beneficeranno notevolmente, altri potrebbero non trarne alcun vantaggio. Ci sono molti pro e contro nell’utilizzo del multithreading, ma se usato correttamente, è possibile ridurre drasticamente il tempo di esecuzione del codice.
Ulteriori letture
Source:
https://adamtheautomator.com/powershell-multithreading/