Semplice descrizione dei thread worker e I / O in .NET

È molto difficile trovare una descrizione dettagliata ma semplice dei thread worker e I / O in .NET

Cosa mi è chiaro riguardo a questo argomento (ma potrebbe non essere tecnicamente preciso):

  • I thread di lavoro sono thread che dovrebbero utilizzare CPU per il loro lavoro;
  • I thread I / O (detti anche “thread delle porte di completamento”) devono utilizzare i driver dei dispositivi per il loro lavoro ed essenzialmente “non fare nulla”, monitorare solo il completamento delle operazioni senza CPU.

Cosa non è chiaro:

  • Sebbene il metodo ThreadPool.GetAvailableThreads restituisca il numero di thread disponibili di entrambi i tipi, sembra che non ci siano API pubbliche per pianificare il lavoro per il thread I / O. È ansible creare manualmente solo thread di lavoro in .NET?
  • Sembra che un singolo thread I / O possa monitorare più operazioni di I / O. È vero? In tal caso, perché ThreadPool ha così tanti thread I / O disponibili di default?
  • In alcuni testi ho letto quella richiamata, triggersta dopo che il completamento dell’operazione I / O è stato eseguito dal thread I / O. È vero? Non è un lavoro per thread di lavoro, considerando che questa callback è un’operazione della CPU?
  • Per essere più specifici, i thread I / O utente delle pagine asincrone ASP.NET? Che cosa è esattamente il vantaggio in termini di prestazioni nel passare da un lavoro di I / O a un thread separato invece di aumentare il numero massimo di thread di lavoro? È perché il singolo thread I / O monitora più operazioni? Oppure Windows fa un cambio di contesto più efficiente quando si usano i thread I / O?

Il termine “thread di lavoro” in .net / CLR si riferisce in genere a qualsiasi thread diverso dal thread Main che esegue un “lavoro” per conto dell’applicazione che ha generato il thread. ‘Lavoro’ potrebbe davvero significare qualsiasi cosa, inclusa l’attesa di alcuni I / O da completare. ThreadPool conserva una cache di thread di lavoro perché i thread sono costosi da creare.

Il termine “Thread I / O” in .net / CLR si riferisce ai thread che il ThreadPool riserva per inviare chiamate callNativeOverlapped da chiamate win32 “sovrapposte” (note anche come “I / O porta di completamento”). Il CLR mantiene la propria porta di I / O di completamento e può associare qualsiasi handle ad esso (tramite l’API ThreadPool.BindHandle). Esempio qui: http://blogs.msdn.com/junfeng/archive/2008/12/01/threadpool-bindhandle.aspx . Molte API .net utilizzano questo meccanismo internamente per ricevere callback NativeOverlapped, sebbene lo sviluppatore .net tipico non lo userà mai direttamente.

Non c’è davvero alcuna differenza tecnica tra “thread di lavoro” e “thread di I / O” – sono entrambi thread normali. Ma CLR ThreadPool mantiene pool separati di ciascuno semplicemente per evitare una situazione in cui l’elevata richiesta di thread di lavoro esaurisce tutti i thread disponibili per inviare callback di I / O nativi, potenzialmente portando a deadlock. (Immagina un’applicazione che utilizza tutti i 250 thread di lavoro, in cui ognuno è in attesa di alcuni I / O da completare).

Lo sviluppatore deve prestare molta attenzione quando gestisce un callback I / O per garantire che il thread I / O venga restituito al ThreadPool – ovvero, il codice di callback I / O dovrebbe eseguire il minimo richiesto per eseguire il callback e quindi restituire il controllo del thread al threadpool CLR. Se è richiesto più lavoro, tale lavoro dovrebbe essere pianificato su un thread di lavoro. In caso contrario, l’applicazione rischia di “dirottare” il pool CLR di thread di I / O di riserva riservati da utilizzare come normali thread di lavoro, determinando la situazione di deadlock descritta in precedenza.

Alcuni buoni riferimenti per ulteriori letture: porte di completamento I / O Win32: http://msdn.microsoft.com/en-us/library/aa365198(VS.85).aspx threadpool gestito: http://msdn.microsoft.com /en-us/library/0ka9477y.aspx esempio di BindHandle: http://blogs.msdn.com/junfeng/archive/2008/12/01/threadpool-bindhandle.aspx

Inizierò con una descrizione di come I / O asincrono viene utilizzato dai programmi in NT.

Si può avere familiarità con la funzione API Win32 ReadFile (come un esempio), che è un wrapper attorno alla funzione Native API NtReadFile . Questa funzione consente di fare due cose con I / O asincrono:

  • È ansible creare un object evento e passarlo a NtReadFile . Questo evento verrà quindi segnalato al termine dell’operazione di lettura.
  • È ansible passare una funzione di chiamata di procedura asincrona (APC) a NtReadFile . In sostanza, ciò significa che quando l’operazione di lettura termina, la funzione verrà accodata al thread che ha avviato l’operazione e verrà eseguita quando il thread esegue un’attesa affidabile .

Vi è tuttavia un terzo modo di essere avvisati quando un’operazione di I / O è completata. È ansible creare un object port di completamento dell’I / O e associarvi i relativi handle. Ogni volta che un’operazione viene completata su un file associato alla porta di completamento I / O, i risultati dell’operazione (come lo stato I / O) vengono accodati alla porta di completamento I / O. È quindi ansible impostare un thread dedicato per rimuovere i risultati dalla coda ed eseguire le attività appropriate come richiamare le funzioni di callback. Questo è essenzialmente ciò che è un “thread di lavoro I / O”.

Un normale “thread di lavoro” è molto simile; invece di rimuovere i risultati I / O da una coda, rimuove gli elementi di lavoro da una coda. È ansible mettere in coda gli elementi di lavoro ( QueueUserWorkItem ) e farli eseguire dai thread di lavoro. Ciò impedisce di generare un thread ogni volta che si desidera eseguire un’attività in modo asincrono.

In poche parole, un thread di lavoro ha lo scopo di eseguire un breve periodo di lavoro e si cancellerà automaticamente una volta completato. Un callback può essere usato per notificare al processo genitore che ha completato o per ritrasferire i dati.

Un thread I / O eseguirà la stessa operazione o serie di operazioni ininterrottamente finché non verrà arrestato dal processo padre. È così chiamato perché generalmente i driver dei dispositivi eseguono continuamente il monitoraggio della porta del dispositivo. Un thread I / O tipicamente crea Eventi ogni volta che desidera comunicare ad altri thread.

Tutti i processi vengono eseguiti come thread. La tua applicazione funziona come una discussione. Qualsiasi thread può generare thread di lavoro o thread I / O (come li chiami).

C’è sempre un buon equilibrio tra le prestazioni e il numero o il tipo di thread utilizzati. Troppi callback o eventi gestiti da un processo peggioreranno notevolmente le prestazioni a causa del numero di interruzioni del ciclo del processo principale man mano che vengono gestiti.

Esempi di thread di lavoro consistono nell’aggiungere dati in un database dopo l’interazione dell’utente o eseguire un lungo calcolo matematico o scrivere dati in un file. Usando un thread di lavoro si libera l’applicazione principale, questo è più utile per le GUI poiché non si blocca mentre viene eseguito l’attività.

Qualcuno con più abilità di me salterà qui per dare una mano.

I thread di lavoro hanno un sacco di stato, sono programmati dal processore ecc. E tu controlli tutto ciò che fanno.

Le porte di completamento dell’IO vengono fornite dal sistema operativo per attività molto specifiche che implicano poco stato condiviso, e quindi sono più veloci da utilizzare. Un buon esempio di .Net è il framework WCF. Ogni “chiamata” a un servizio WCF viene effettivamente eseguita da una Porta di completamento IO perché sono i più veloci da avviare e il sistema operativo li segue per voi.