8

Sulla base di numerosi libri e blog tra cui this excellent one here, è chiaro che quando si scrive una libreria dll che espone metodi asincroni helper cioè i metodi wrapper, è generalmente considerata una best practice per internamente completare l'operazione di I/O di metodi asincrone attuali su un filo di thread in questo modo (pseudo codice riportato di seguito per brevità e sto usando HttpClient come esempio)C# asincrono/attendi concatenamento con ConfigureAwait (falso)

public Async Task<HttpResponseMessage> MyMethodAsync(..) 
{ 
    ... 
    var httpClient = new HttpClient(..); 
    var response = await httpClient.PostAsJsonAsync(..).ConfigureAwait(false); 
    ... 
    return response; 
} 

la chiave qui è l'utilizzo di ConfigureAwait(false) affinché Il completamento delle attività di I/O si verifica su un thread del threadpool anziché sul contesto del thread originale, evitando potenzialmente i deadlock.

La mia domanda è dal punto di vista di un chiamante. Sono particolarmente interessato a uno scenario in cui vi siano livelli di chiamate di metodo tra il chiamante e la chiamata al metodo precedente, come illustrato nel seguente esempio.

CallerA -> Method1Async -> Method2Async -> finally the above MyMethodAsync 

E 'sufficiente avere ConfigureAwait(false) sul metodo finale unica o si dovrebbe inoltre garantire Method1Async e Method2Async anche internamente chiamare i loro metodi asincroni con ConfigureAwait(false)? Sembra strano averlo incluso in tutti questi metodi di intermediazione, specialmente se Method1Async e Method2Async sono semplicemente sovraccarichi che finiscono per chiamare MyMethodAsync. Qualche idea, per favore ci illumini!

Aggiornato con Esempio Quindi, se ho una libreria con il seguente metodo asincrono privato,

private async Task<string> MyPrivateMethodAsync(MyClass myClass) 
{ 
    ... 
    return await SomeObject.ReadAsStringAsync().ConfigureAwait(false); 
} 

devo assicurarsi che i seguenti metodi di overload pubblici sia includono anche ConfigureAwait (false) come mostrato di seguito?

public async Task<string> MyMethodAsync(string from) 
{ 
     return await MyPrivateMethodAsync(new (MyClass() { From = from, To = "someDefaultValue"}).ConfigureAwait(false); 
} 
public async Task<string> MyMethodAsync(string from, string to) 
{ 
     return await MyPrivateMethodAsync(new (MyClass() { From = from, To = to }).ConfigureAwait(false); 
} 
+0

Durante l'utilizzo di 'ConfigureAwait (false)' è una buona pratica per un codice libreria, il suo utilizzo potrebbe avere le sue implicazioni: http://stackoverflow.com/q/28410046 – Noseratio

risposta

9

Decisamente no. ConfigureAwait proprio come suggerisce il nome, configura lo await. Ha effetto solo sullo await accoppiato con esso.

ConfigureAwait in realtà restituisce un tipo diverso awaitable, ConfiguredTaskAwaitable invece di Task che a sua volta restituisce un diverso tipo awaiter ConfiguredTaskAwaiter invece di TaskAwaiter

Se si vuole ignorare il SynchronizationContext per tutto il vostro await s è necessario utilizzare ConfigureAwait(false) per ogni di loro.

Se si vuole limitare l'uso di ConfigureAwait(false) è possibile utilizzare il mio NoSynchronizationContextScope (vedi here) in cima:

async Task CallerA() 
{ 
    using (NoSynchronizationContextScope.Enter()) 
    { 
     await Method1Async(); 
    } 
} 
+1

Penso che "deve ... per * ogni * di loro "è un po 'troppo severo - hai 2+' await' in una riga e il primo in realtà restituisce in modo asincrono che il resto non ha bisogno di 'ConfigureAwait (false)' perché hai perso il contesto quando la chiamata ritorna ... Ma in realtà 'ConfigureAwait (false)' dovrebbe essere su tutte le chiamate (o nessuna). –

+1

@ i3arnon una breve domanda sul tuo NoSynchronizationContextScope che sembra ottimo e vorrei capire il suo caso d'uso. Seguendo il mio esempio sopra, ru dicendo che, con un po 'di codifica aggiuntiva per includere NoSynchronizationContextScope, CallerA ora può istruire i metodi sulla struttura di chiamata, ad esempio Method1Async -> Method2Async -> fino a MyMethodAsync per ignorare temporaneamente SC indipendentemente dal fatto o non questi metodi hanno ConfigureAwait (falso) in loro? – SamDevx

+1

@SamDevx praticamente. In realtà è più duro di quello. Rimuove temporaneamente il SC in modo che i metodi interni non abbiano un SC da ignorare. – i3arnon

4

Quando l'attività è in attesa, si crea una traccia corrispondente TaskAwaiter per mantenere la compito che cattura anche l'attuale SynchronizationContext. Dopo che l'attività è stata completata, l'utente attende il codice dopo l'attesa (chiamata continuazione) in quel contesto catturato.

Si può evitare che chiamando ConfigureAwait(false), che crea un diverso tipo di awiatable (ConfiguredTaskAwaitable) e il suo corrispondente awaiter (ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) che non esegue la continuazione del contesto catturato.

Il punto è che per ogni await, viene creata una diversa istanza di un cameriere, non è qualcosa che è condivisa tra tutti gli attendibili nel metodo o nel programma. Pertanto, è meglio chiamare ConfigureAwait(false) per ciascuna istruzione await.

È possibile visualizzare il codice sorgente per gli spettatori here.

+0

Una correzione minore: quando viene creato un 'Task', * non * acquisisce il' SynchronizationContext 'corrente. Quando viene chiamato 'Task.GetAwaiter()' (dal codice generato dal compilatore per 'await'), a quel punto' SynchronizationContext' viene catturato. – Noseratio

+0

@Noseratio grazie per il tuo commento. L'ho ottenuto dalla [Fonte di attività] (http://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/TaskAwaiter.cs,16e40fc537484d93). 'Task.GetAwaiter()' non sembra farlo, anche se pensavo che fosse così. Devo mancare qualcosa –

+0

Non ho detto che "TaskAwaiter' cattura SC quando viene creato. Lo fa nella sua implementazione di "ICriticalNotifyCompletion.OnCompleted/UnsafeOnCompleted' [qui] (http://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/TaskAwaiter.cs.45f10a20f8fdfd61,referenze), che è la chiave metodo di ogni cameriere. – Noseratio

Problemi correlati