Il presente articolo è stato tradotto automaticamente.
Programmazione asincrona
Pausa e riproduzione con attesa
Mads Torgersen
Scaricare il codice di esempio
I metodi asincroni nelle prossime versioni di Visual Basic e C# rappresenteranno un modo eccezionale per estrarre i callback dalla programmazione asincrona. In questo articolo analizzerò ingrandire occhiata in attesa che la nuova parola chiave viene eseguito effettivamente, iniziando dal livello concettuale e lavorando verso il basso per il ferro.
Composizione sequenziale
Visual Basic e C# sono imperative linguaggi di programmazione, ovvero e orgogliosi di esso! Ciò significa che excel consente di express la logica di programmazione come un sequenza di operazioni discrete, da svolgere uno dopo l'altro. Livello di istruzione più costrutti del linguaggio sono strutture di controllo che offrono diversi modi per specificare l'ordine in cui verranno eseguiti i passaggi discreti di un corpo di codice:
- Istruzioni condizionali, quali if e switch consentono di scegliere diverse azioni successive in base allo stato corrente del mondo.
- Il ciclo di istruzioni, ad esempio for, foreach e mentre consentono di ripetere l'esecuzione di una determinata serie di passaggi più volte.
- Continuare ad esempio istruzioni, throw e goto consentono trasferire il controllo non localmente per altre parti del programma.
Creazione di una logica utilizzando i risultati di strutture di controllo in composizione sequenziale, e questo è il fulcro di programmazione imperativa. È infatti per questo motivo esistono molte strutture di controllo per scegliere tra: desiderate sequenziale composizione sia veramente comoda e ben strutturato.
Esecuzione continua
Nei linguaggi più imperative, comprese le versioni correnti di Visual Basic e C#, l'esecuzione di metodi (o funzioni, procedure o tutto ciò è possibile chiamarli) è continue. Che cosa significa, che consiste nel fatto che una volta che un thread di controllo ha iniziato l'esecuzione di un determinato metodo, esso verrà costantemente occupato in questo modo, fino al termine dell'esecuzione del metodo. Sì, in alcuni casi il thread determina l'esecuzione istruzioni nei metodi chiamati dal corpo di codice, ma questo è solo una parte dell'esecuzione del metodo. Il thread si passerà mai per eseguire qualsiasi operazione per che del metodo non richiesti.
La continuità in alcuni casi è problematica. In alcuni casi non richiede un metodo da eseguire per verificare lo stato di avanzamento, ovvero tutto ciò che può fare è in attesa di un intervento: un download, un accesso ai file, un calcolo che si verificano su un thread diverso, un certo punto nel tempo per arrivare. In tal caso il thread totalmente occupato eseguire alcuna operazione. Il termine comune che è che il thread è bloccati; il metodo che lo ha causato a questo scopo è detto blocco.
Di seguito è riportato un esempio di un metodo che gravemente blocca:
static byte[] TryFetch(string url)
{
var client = new WebClient();
try
{
return client.DownloadData(url);
}
catch (WebException) { }
return null;
}
Un thread in esecuzione questo metodo verrà stand ancora durante la maggior parte della chiamata al client.DownloadData non esegue alcuna operazione effettiva ma solo in attesa.
Ciò risulta danneggiato quando i thread sono preziosi, e sono spesso. Su un livello intermedio tipico, ogni richiesta per la manutenzione a sua volta richiede una comunicazione con un server back-end o altri servizi. Se ogni richiesta viene gestita da un thread separato e i thread sono prevalentemente bloccati in attesa dei risultati intermedi, il gran numero di thread nel livello intermedio può trasformarsi facilmente un collo di bottiglia delle prestazioni.
Il tipo più prezioso del thread è probabilmente un thread dell'interfaccia utente: non vi è solo uno di essi. Praticamente tutte le infrastrutture di interfaccia utente sono a thread singolo e tutto ciò che richiedono legate all'interfaccia utente, ovvero gli eventi, gli aggiornamenti, la logica di manipolazione dell'interfaccia utente dell'utente, ovvero eseguite sullo stesso dedicato thread. Se una di queste attività (ad esempio, un gestore eventi scelta per il download da un URL) viene avviato in attesa, l'intera interfaccia utente non è in grado di compiere progressi perché il thread è talmente occupato assolutamente nulla.
Ciò che serve è un modo per più attività sequenziale per poter condividere i thread. A tale scopo, essi devono a volte "una pausa", vale a dire, lasciare i fori nella loro esecuzione in cui altri utenti possono eseguire una determinata operazione sullo stesso thread. In altre parole, in alcuni casi devono essere discontinua. È particolarmente utile se tali attività sequenziale che l'istruzione break mentre che svolgono nulla comunque. Per i battelli di emergenza: la programmazione asincrona!
Programmazione asincrona
Oggi, poiché i metodi sono sempre continui, è necessario suddividere le attività discontinue (ad esempio la prima e dopo del download di un) in più metodi. Per scrivere in memoria un foro al centro di esecuzione del metodo, è necessario tear a distanza nel suo bit continuo. Le API possono aiutare grazie alla presenza di versioni asincrone dei metodi di esecuzione prolungata che consentono di avviare l'operazione (Avvia il download, ad esempio) (non bloccante), memorizzare un callback passato per l'esecuzione al termine dell'operazione e restituiscono immediatamente al chiamante. Ma affinché il chiamante fornisca la richiamata, l'attività "dopo" fattorizzato un metodo separato.
Ecco il funzionamento del metodo TryFetch precedente:
static void TryFetchAsync(string url, Action<byte[], Exception> callback)
{
var client = new WebClient();
client.DownloadDataCompleted += (_, args) =>
{
if (args.Error == null) callback(args.Result, null);
else if (args.Error is WebException) callback(null, null);
else callback(null, args.Error);
};
client.DownloadDataAsync(new Uri(url));
}
Qui è possibile visualizzare un paio di modi diversi di callback di passaggio: metodo DownloadDataAsync lo prevede che un gestore di eventi sono stati firmati all'evento DownloadDataCompleted, in modo che sia il modo in cui si passa la parte "dopo" del metodo. TryFetchAsync se stesso deve anche gestire i callback relativi chiamanti. Impostazione di tale azienda intero evento personalmente, è possibile utilizzare l'approccio più semplice è sufficiente adottare un callback come parametro. È una cosa positiva, è possibile utilizzare un'espressione lambda per il gestore eventi in modo da appena possibile acquisire e utilizzare il parametro "callback" direttamente; Se si è tentato di utilizzare un metodo denominato, è necessario pensare a un modo per ottenere il delegato di callback per il gestore eventi. È sufficiente sospendere per un secondo e considerare il modo in cui è necessario scrivere questo codice senza funzione lambda.
Ma la cosa principale da sottolineare è stato modificato in quanto il flusso di controllo. Invece di utilizzare le strutture di controllo del linguaggio per esprimere il flusso, è possibile emulare li:
- L'istruzione return viene emulato chiamando la richiamata.
- Richiamando il callback viene emulato implicita propagazione delle eccezioni.
- La gestione delle eccezioni viene emulato con una verifica del tipo.
Naturalmente, questo è un esempio molto semplice. Come il controllo desiderato struttura diventa sempre più complessa, emulando si ottiene ancora più così.
Riassumendo, abbiamo acquisito discontinuità e in tal modo la possibilità di esecuzione dei thread a fare qualcosa di diverso mentre "in attesa" per il download. Ma non abbiamo perso la facilità di utilizzo di strutture di controllo per esprimere il flusso. Abbiamo abbia lasciato il nostro patrimonio come un linguaggio imperativo strutturato.
Metodi asincroni
Quando si esamina il problema in questo modo, diventa la Guida in linea di metodi asincroni chiaro come nelle versioni in avanti di Visual Basic e C#: essi consentono di express codice sequenziale discontinua.
Diamo un'occhiata alla versione asincrona di TryFetch con la nuova sintassi:
static async Task<byte[]> TryFetchAsync(string url)
{
var client = new WebClient();
try
{
return await client.DownloadDataTaskAsync(url);
}
catch (WebException) { }
return null;
}
I metodi asincroni consentono di eseguire l'inline di interruzione, all'interno di codice: non solo è possibile utilizzare le strutture di controllo preferita per esprimere la composizione sequenziale, è anche possibile poke fori per l'esecuzione con le espressioni in attesa, ovvero i fori in cui l'esecuzione del thread è libero di eseguire altre operazioni.
È un buon metodo per pensare a immaginare che i metodi asincroni sono presenti pulsanti "Pausa" e "play". Quando l'esecuzione del thread raggiunge un'espressione await, raggiunge il pulsante "pause" e viene sospesa l'esecuzione del metodo. Quando viene completata l'attività venga sollecitata, raggiunge il pulsante "play" e l'esecuzione del metodo è ripreso.
La riscrittura del compilatore
Quando un'operazione complessa ricerca semplice, in genere significa che vi è qualcosa di interessante succede dietro le quinte, e certamente nel caso di metodi asincroni. La semplicità fornisce un'astrazione di Nizza che rende molto più semplice di scrittura e lettura di codice asincrono. Non è necessario comprendere ciò che avviene di sotto. Ma se si comprende, sarà sicuramente utile è diventare un programmatore più asincrono ed essere in grado di utilizzare più pienamente la funzione. E, se state leggendo questo articolo, ci sono buone chi è anche semplice probabilità curiosi. Così entriamo: cosa i metodi asincroni, ovvero ed espressioni await in essi contenuti, ovvero effettivamente?
Quando il compilatore Visual Basic o C#, viene attesa di un metodo asincrono, esso mangles abbastanza durante la compilazione: la discontinuità del metodo non è supportata direttamente da Common Language runtime sottostante e deve essere emulata dal compilatore. Pertanto, invece di dover inserire il metodo distanziati in bit, il compilatore esegue. Tuttavia, questa operazione viene effettuata molto diverso del si sarebbe probabilmente eseguita manualmente.
Il compilatore trasforma il metodo asincrono in un statemachine. Tiene traccia di macchina a stati in cui è in esecuzione e qual è lo stato del locale. Può essere con o sospesa. Quando è in esecuzione, esso può raggiungere un await, che colpisce il pulsante "pause" e sospende l'esecuzione. Quando esso viene sospesa, qualcosa può cliccare sul pulsante "play" per tornare indietro e in esecuzione.
L'espressione await è responsabile dell'impostazione cose in modo che ottiene premuto il pulsante "play" al completamento del task atteso. Prima di passare a che, tuttavia, esaminiamo della macchina a stati se stesso e che cosa veramente sono i pulsanti Riproduci e pausa.
Generatori di attività
I metodi asincroni di produrre le attività. Più specificamente, un metodo asincrono restituisce un'istanza di uno dei tipi di attività o <T> da System.Threading.Tasks e tale istanza viene generato automaticamente. Non deve necessariamente essere (e non può essere) fornito dal codice utente. (Si tratta di un piccolo lie: i metodi asincroni possono restituire void, ma ci sarà ignorare che per il momento.)
Dal punto di vista del compilatore, le attività di produzione è estremamente semplice. Esso si basa su una nozione di un generatore di attività, trovata in System.Runtime.CompilerServices (poiché normalmente non è destinato al consumo umano diretto) fornito dal framework. Ad esempio, non vi è un tipo simile al seguente:
public class AsyncTaskMethodBuilder<TResult>
{
public Task<TResult> Task { get; }
public void SetResult(TResult result);
public void SetException(Exception exception);
}
Il generatore consente al compilatore di ottenere un'attività e quindi consenta completa l'attività con un risultato o un'eccezione. Figura 1 è uno sketch di questa macchina come appare per TryFetchAsync.
Figura 1 Creazione di un'attività
static Task<byte[]> TryFetchAsync(string url)
{
var __builder = new AsyncTaskMethodBuilder<byte[]>();
...
Action __moveNext = delegate
{
try
{
...
return;
...
__builder.SetResult(…);
...
}
catch (Exception exception)
{
__builder.SetException(exception);
}
};
__moveNext();
return __builder.Task;
}
Fate attenzione:
- Viene innanzitutto creato un generatore.
- Quindi viene creato un delegato di __moveNext. Questo delegato è il pulsante "play". Viene chiamato il delegato di ripresa e contiene:
- L'originale del codice dal metodo asincrono (anche se ci abbiamo eliminato finora).
- Restituire le istruzioni, che rappresentano inserendo il pulsante "pause".
- Chiamate che completano il generatore con esito positivo, che corrispondono alle istruzioni return del codice originale.
- Un ritorno a capo del try/catch che completa il generatore di tutte le eccezioni di escape.
- A questo punto viene premuto il pulsante "play"; viene chiamato il delegato di ripresa. Viene eseguito fino a quando non viene raggiunto il pulsante "pause".
- L'attività viene restituito al chiamante.
I generatori di attività sono tipi di supporto speciale solo destinati al consumo del compilatore. Tuttavia, il comportamento non è molto diverso da che cosa accade quando si utilizzano direttamente i tipi di TaskCompletionSource di attività Parallel Library (TPL).
Finora ho creato un pulsante "play" e un'attività per tornare, ovvero il delegato di ripresa, ovvero per un utente da chiamare quando si è il momento di riprendere l'esecuzione. È necessario vedere modalità di riprendere l'esecuzione e come l'espressione await impostati per un elemento a tale scopo. Prima riunire tutto, però, diamo un'occhiata al modo in cui le attività vengono consumate.
Awaitables e Awaiters
Come si è visto, le attività possono essere sollecitate. Tuttavia, Visual Basic e C# sono perfettamente soddisfatti in attesa di altre operazioni, purché si trovino awaitable; in altre parole, se dispone di una determinata forma può essere compilata l'espressione await contro. Per poter essere awaitable, un elemento deve avere un metodo GetAwaiter, che a sua volta restituisce un awaiter. Ad esempio, attività <TResult> dispone di un metodo GetAwaiter che restituisce il seguente tipo:
public struct TaskAwaiter<TResult>
{
public bool IsCompleted { get; }
public void OnCompleted(Action continuation);
public TResult GetResult();
}
I membri sull'awaiter di lasciare che il compilatore controllare se il awaitable è già completata, sottoscrivere una richiamata se non è ancora e ottenere il risultato (eccezione) quando si trova.
A questo punto è possibile iniziare a vedere che cosa è necessario fare una await per sospendere e riprendere intorno al awaitable. Ad esempio, await all'interno del nostro esempio TryFetchAsync atta a rendere simile al seguente:
__awaiter1 = client.DownloadDataTaskAsync(url).GetAwaiter();
if (!__awaiter1.IsCompleted) {
...
// Prepare for resumption at Resume1
__awaiter1.OnCompleted(__moveNext);
return; // Hit the "pause" button
}
Resume1:
...
__awaiter1.GetResult()) ...
Ancora una volta, Guarda cosa succede:
- Viene ottenuto un awaiter per l'attività ha restituito da DownloadDataTaskAsync.
- Se il awaiter non è stata completata, il pulsante "play", ovvero il delegato di ripresa, ovvero viene passato a awaiter come un callback.
- Quando il awaiter riprende l'esecuzione (at Resume1) il risultato è ottenuto e utilizzato nel codice che segue.
Il caso comune è pertanto evidente che il awaitable è un'attività o <T>. In realtà, questi tipi, ovvero che sono già presenti in Microsoft.NET Framework 4, sono state ottimizzate profondamente per questo ruolo. Tuttavia, esistono buone ragioni per consentire anche altri tipi di awaitable:
- Per altre tecnologie di bridging: F#, ad esempio, dispone di un tipo Async <T> che corrisponde approssimativamente a Func < attività <T> >. La possibilità di attesa Async <T> direttamente da Visual Basic e C# aiuta a ponte tra codice asincrono, scritto in due lingue. F# allo stesso modo espone funzionalità di bridging per spostarsi nella direzione opposta, ovvero che utilizza le attività direttamente nel codice F# asincrono.
- Implementa la semantica speciale: The TPL stesso consiste nell'aggiungere alcuni semplici esempi di questo. Il metodo di utilità Task.Yield statico, ad esempio, restituisce un awaitable che saranno sembrano (tramite IsCompleted) non è completa, ma immediatamente pianificare il callback passato al metodo, come se in realtà compiuti. Ciò consente di forzare la programmazione e ignorare l'ottimizzazione del compilatore di verrà ignorato se il risultato è già disponibile. Può essere utilizzato per scrivere in memoria i fori in codice "live" e migliorare i tempi di risposta di codice che non è inattivo. Attività stesse non possono rappresentare cose che sono state completate ma sembrano non essere, in modo che un tipo speciale di awaitable è utilizzato a questo.
Rendere uno sguardo ulteriormente l'implementazione di awaitable di attività, è opportuno terminare osservando il compilatore riscrittura del metodo asincrono e completare la contabilità consente di tenere traccia dello stato di esecuzione del metodo.
La macchina a stati
Per unire tutto insieme, è necessario per la creazione di una macchina a stati intorno alla produzione e consumo delle attività. In pratica, tutta la logica di un utente dal metodo originale viene inserita nel delegato la ripresa, ma le dichiarazioni di variabili locali sono sollevate in modo che essi possano sopravvivere per più richiami. Inoltre, è stata introdotta una variabile di stato per tenere traccia di quanto cose hanno utilizzato e la logica dell'utente nel delegato la ripresa viene eseguito il wrapping in un interruttore big che esamina lo stato e si passa a un'etichetta corrispondente. Pertanto, ogni volta che viene chiamata la ripresa, passerà assente in cui è stata interrotta l'ultima volta. Figura 2 inserisce tutto insieme.
Figura 2 Creazione di una macchina a stati
static Task<byte[]> TryFetchAsync(string url)
{
var __builder = new AsyncTaskMethodBuilder<byte[]>();
int __state = 0;
Action __moveNext = null;
TaskAwaiter<byte[]> __awaiter1;
WebClient client = null;
__moveNext = delegate
{
try
{
if (__state == 1) goto Resume1;
client = new WebClient();
try
{
__awaiter1 = client.DownloadDataTaskAsync(url).GetAwaiter();
if (!__awaiter1.IsCompleted) {
__state = 1;
__awaiter1.OnCompleted(__moveNext);
return;
}
Resume1:
__builder.SetResult(__awaiter1.GetResult());
}
catch (WebException) { }
__builder.SetResult(null);
}
catch (Exception exception)
{
__builder.SetException(exception);
}
};
__moveNext();
return __builder.Task;
}
Analizzerò abbastanza! Siamo certi che vi state chiedendo se stessi perché questo codice è molto più dettagliato rispetto alla versione manualmente "sincrona" illustrata in precedenza. Esistono un paio di buoni motivi, tra cui l'efficienza (allocazioni minori in linea generale) e Generalità (valido per awaitables definito dall'utente, non solo operazioni). Tuttavia, il motivo principale è questo: non è necessario prelevare; dopotutto distanti la logica dell'utente è appena integrare, con alcuni salti e restituisce e simili.
Mentre l'esempio è troppo semplice realmente giustificano, riscrittura logica del metodo in un set semanticamente equivalente dei metodi distinti per ogni bit continua della logica tra il resta in attesa è molto difficile business. Più strutture di controllo del è in attesa sono nidificate, viene ottenuto il peggio. Quando non solo cicli istruzioni continua e break ma blocchi try-finally e persino le istruzioni goto circondano la resta in attesa, è estremamente difficile, se è effettivamente possibile, per produrre una riscrittura con alta fedeltà.
Invece di provare che, sembra un trucco ben consiste nel sovrapporre semplicemente il codice dell'utente originale con un altro livello di struttura di controllo, sono disponibili in airlifting (con i salti condizionali) e out (resi) da quando la situazione richiede. Riprodurre e mettere in pausa. Presso Microsoft, ci abbiamo sistematicamente testato l'equivalenza dei metodi asincroni per le loro controparti sincroni e abbiamo abbiamo chiesto conferma che si tratta di un approccio molto efficace. Non vi è modo migliore per mantenere la semantica sincrona nell'area di autenticazione asincrona rispetto a conservando il codice che descrive in primo luogo tali semantica.
La stampa
La descrizione che ho fornito è leggermente idealized, esistono alcune ulteriori suggerimenti per la riscrittura, potrebbe avere sospetti. Ecco alcuni degli altri problemi individuati che il compilatore deve confrontarsi con:
Istruzioni goto di rewrite in nella figura 2 non viene effettivamente compilato, in quanto le istruzioni goto (in C# almeno) Impossibile passare a etichette sotterrate in strutture nidificate. Non ci sono problemi di per sé, come il compilatore genera intermediate language (IL), non di codice sorgente e non viene disturbato tramite nidificazione. Ma anche the non consente un tuffo in all'interno di un blocco try, così come avviene nel mio esempio. Che cosa accade veramente è invece che passa all'inizio di un blocco try, immettere normalmente e quindi passare e passare nuovamente.
Blocchi finally quando si torna esaurito il delegato di ripresa a causa di un await, non si desidera l'infine gli organismi incaricati di ancora eseguita. Deve essere salvati per quando il originale restituire vengono eseguite istruzioni dal codice utente. Che controllano generando un flag booleano segnalazione se l'infine gli organismi devono essere eseguiti e in modo da integrare a check.
Ordine di valutazione un'espressione await non è necessariamente il primo argomento a un metodo o di un operatore; può apparire nella parte centrale. Per mantenere l'ordine di valutazione, tutti gli argomenti precedenti devono essere valutati prima del await e di memorizzarle e il loro recupero nuovamente dopo il await rispettati.
In cima a tutto questo, esistono alcune limitazioni, che non è possibile ottenere intorno. Ad esempio, è in attesa non sono consentiti all'interno di un blocco catch o finally blocco, poiché non sappiamo di un buon metodo per ristabilire il contesto dell'eccezione a destra dopo il await.
La Awaiter di attività
Awaiter utilizzato dal codice generato dal compilatore per implementare l'espressione await ha una notevole libertà quanto alla pianificazione del delegato di ripresa, vale a dire il resto del metodo asincrono. Tuttavia, lo scenario dovrebbe essere davvero avanzate prima che sarebbe necessario implementare il proprio awaiter. Attività stesse hanno una quantità notevole di flessibilità in modalità pianificazione perché essi rispettano un concetto di contesto stesso è collegabile di programmazione.
Il contesto di programmazione è uno di tali nozioni che probabilmente sarebbe un po' realizzare se ci fosse stata progettate dall'inizio. Come se fosse, esso è composto dal esistenti alcuni concetti che ci abbiamo deciso di non perdere tempo risalga ulteriormente durante il tentativo di introdurre un concetto unificando in primo piano. Diamo un'occhiata all'idea a livello concettuale, e quindi verrà approfondito la realizzazione.
La filosofia di sostenere la programmazione delle richiamate asincrone per le attività attesissimo è che si desidera continuare l'esecuzione "in cui trovavano prima," per un valore di "where". Si tratta "where" che ho chiamato il contesto di programmazione. Contesto di programmazione è un concetto thread affine; ogni thread (al massimo) è uno. Quando è in esecuzione su un thread, è possibile richiedere per il contesto di programmazione è in esecuzione e quando si dispone di un contesto di programmazione, è possibile pianificare operazioni da eseguire al suo interno.
In modo che si tratta di un metodo asincrono dovrebbe cosa quando resta in attesa di un'attività:
- Sulla sospensione: chiedere il thread è in esecuzione su per il contesto di programmazione.
- Nella ripresa: pianificazione indietro il delegato di ripresa in tale contesto di programmazione.
Perché è importante? Prendere in considerazione il thread dell'interfaccia utente. Esso ha il proprio contesto di pianificazione, distribuzione nuovo lavoro mediante l'invio attraverso la coda dei messaggi sul thread dell'interfaccia utente. Ciò significa che se si sta eseguendo sul thread dell'interfaccia utente, un'attività, in attesa quando il risultato dell'attività è pronto al resto del metodo asincrono eseguirà nuovamente sul thread dell'interfaccia utente. In questo modo, tutte le operazioni da eseguire solo sul thread dell'interfaccia utente (manipolare l'interfaccia utente) è ancora possibile farlo dopo await; si non si verifica un "hop thread" strano all'interno di codice.
Altri contesti di programmazione sono multithread; in particolare, il pool di thread standard è rappresentato da un unico contesto di programmazione. Quando viene programmato nuovi ad essa, può andare su uno qualsiasi dei thread del pool. In questo modo, è possibile che un metodo asincrono che viene avviato in esecuzione nel pool di thread continuerà a tale scopo, se essa potrebbe essere "hop intorno" tra i diversi thread specifico.
In pratica, non esiste il concetto unico corrispondente al contesto di programmazione. Dal punto di vista più o meno, SynchronizationContext del thread agisce come relativo contesto di programmazione. In tal caso, se un thread è uno di questi (concetto un esistente che può essere implementato dall'utente), verrà utilizzato. In caso contrario, viene utilizzata l'utilità di pianificazione del thread (un concetto simile introdotto dal TPL). Se non è presente uno di questi, viene utilizzato il valore predefinito di utilità di pianificazione; tale pianifica resumptions al pool di thread standard.
Naturalmente, tutto questo settore pianificazione ha influisce sulle prestazioni. In genere, in scenari di utilizzo, è trascurabile e sicuramente premiato: con l'interfaccia utente spezzettati in bit gestibile il lavoro effettivo dal vivo e pompata attraverso il message pump non appena sono disponibili i risultati di attesa per il codice è normalmente solo ciò che il medico ordinati in.
In alcuni casi, anche se, soprattutto nel codice della libreria, ovvero possono le cose diventano troppo dettagliate. Tenere presente quanto segue:
async Task<int> GetAreaAsync()
{
return await GetXAsync() * await GetYAsync();
}
Questa pianificazione torna al contesto della programmazione due volte, ovvero dopo ogni await — è sufficiente per eseguire una moltiplicazione sul thread "right". Ma chi Importa sapere quale thread che sta moltiplicando su? Che è probabilmente inutili (se utilizzate spesso), e non esistono trucchi per evitarlo: È possibile essenzialmente disporre l'attesissimo attività in un awaitable non attività che è in grado di disattivare il comportamento di pianificazione-back e appena eseguita la ripresa in qualsiasi altro thread ha completato l'attività, evitando la commutazione di contesto e il ritardo di programmazione:
async Task<int> GetAreaAsync()
{
return await GetXAsync().ConfigureAwait(continueOnCapturedContext: false)
* await GetYAsync().ConfigureAwait(continueOnCapturedContext: false);
}
Meno pretty, per essere certi, ma un trucco ben da utilizzare nel codice della libreria che finisce per essere un collo di bottiglia per la pianificazione.
Vai via e Async'ify
A questo punto si deve avere una buona conoscenza di base di metodi asincroni. Probabilmente i punti più utili da privare sono:
- Il compilatore consente di mantenere il significato delle strutture di controllo mantenendo effettivamente le strutture di controllo.
- I metodi asincroni non pianificare nuovi thread, ovvero permettono multiplex a quelli esistenti.
- Quando l'attività ottenere sollecitate, essi reinserito è "Dov'eri" per una definizione ragionevole di che cosa significa.
Se sei come me, è già stato alternando la lettura di questo articolo e digitando parte del codice. È stata multiplex più flussi di controllo, ovvero la lettura e scrittura del codice, ovvero nello stesso thread: si. Si tratta esattamente quali sono i metodi asincroni consentono di eseguire.
Mads Torgersen è principal program manager del team di C# e Visual Basic lingua di Microsoft.
Grazie all'esperto tecnico riportato di seguito per la revisione di questo articolo: Stephen Toub