2016-04-15 15 views
7

Sto chiamando un metodo di libreria asincrona con ConfigureAwait (false). Ma, finisco sempre con lo stallo. (Lo sto usando nell'API controller ASP.NET) Ma, se utilizzo lo stesso metodo incluso in Task.Run(), funziona perfettamente.Perché ConfigureAwait (false) non funziona mentre Task.Run() funziona?

La mia comprensione è, Se il metodo di libreria non utilizza ConfigureAwait internamente, l'aggiunta di ConfigureAwait non risolverà il problema poiché nella chiamata alla libreria si verificherà un deadlock (si bloccherà su di esso utilizzando .Result). Ma, se questo è il motivo per cui funziona in Task.Run() in quanto non riuscirà a continuare nello stesso contesto/thread.

Questo article ne parla. A proposito, ho preparato molti articoli di Stephen Cleary. Ma, perché Task.Run() funziona è un mistero.

Codice frammento:

// This Create Method results in Deadlock 
public async Task<string> Create(MyConfig config) 
{ 
     Document doc = await Client.CreateDocumentAsync(CollectionUri, config).ConfigureAwait(false); 
     return doc.Id; 
} 

// Uses Task.Run() which works properly, why?? 
public string Create(MyConfig config) 
{ 
    Document doc = Task.Run(() => Client.CreateDocumentAsync(CollectionUri, config)).Result; 
    return doc.Id; 
} 

[HttpPost] 
public ActionResult CreateConfig(MyConfig config) 
{ 
    string id = Create(config).Result; 
    return Json(id); 
} 

risposta

5

Nel primo esempio, l'implementazione di Client.CreateDocumentAsync è deadlock perché sta cercando di eseguire una continuazione con la corrente SynchronizationContext.

Quando si utilizza Task.Run, il delegato verrà richiamato su una discussione ThreadPool, ciò significa che non ci sarà corrente SynchronizationContext, quindi tutte le continuazioni verranno riprese utilizzando un thread ThreadPool. Ciò significa che non si bloccherà.

Per motivi di interesse, perché il tuo metodo CreateConfig non è asincrono? Le versioni più recenti di MVC e WebAPI supportano metodi asincroni, eliminando lo .Result sarebbe la soluzione migliore.

+0

Non si dovrebbe utilizzare ConfigureAwait (false) per garantire la continuazione utilizzando ThreadPool? e sì, ho reso async il metodo CreateConfig. Ma, volevo capire meglio il concetto esattamente sul perché Task.Run() non crea alcun problema. –

+4

@KrunalModi 'ConfigureAwait (false)' configura solo le continuazioni per utilizzare il threadpool, non l'invocazione di 'Client.CreateDocumentAsync' – Lukazoid

+0

@KrunalModi non ha senso usare' ConfigureAwait' con una chiamata asincrona che internamente ha un'altra chiamata asincrona che * non * è usato con 'ConfigureAwait'. 'ConfigureAwait (false)' dovrebbe essere usato * quasi * ovunque, e specialmente nelle librerie. Solo le chiamate di livello superiore (quelle nel contesto di sincronizzazione dell'interfaccia utente) non hanno bisogno di 'ConfigureAwait (false)' perché la continuazione deve essere eseguita nel contesto di sincronizzazione dell'interfaccia utente. Per le librerie che non utilizzano 'ConfigureAwait (false)' internamente, usando 'attende Task.Run (() => ...)' o 'attende Task.Run (() => ...). ConfigureAwait (false) 'è una buona soluzione. –

4

Credo che Lukazoid sia corretto. Per dirla in un altro modo ...

// This Create Method results in Deadlock 
public async Task<string> Create(MyConfig config) 
{ 
    Document doc = await Client.CreateDocumentAsync(CollectionUri, config).ConfigureAwait(false); 
    return doc.Id; 
} 

È possibile non solo attaccare un livello ConfigureAwait(false) in uno e lo hanno magicamente evitare situazioni di stallo. ConfigureAwait(false) può impedire i deadlock solo se viene utilizzato da ogni await nella chiusura transitiva di tale metodo e in tutti i metodi che chiama.

In altre parole, ConfigureAwait(false) deve essere utilizzato per ogni await in Create (che è), e deve anche essere utilizzato per ogni await in CreateDocumentAsync (che non conosciamo), e ha anche bisogno di essere utilizzato per ogni await in ogni metodo che chiamate CreateDocumentAsync, ecc.

Questo è uno dei motivi per cui è una così "soluzione" fragile al problema del deadlock.

+0

Ogni chiamata asincrona dovrebbe essere adeguatamente documentata su tale argomento al fine di prendere la decisione giusta.Usando 'Attendi [chiamata asincrona] .ConfigureAwait (false)' o 'aspetta Task.Run (async() => attende [chiamata asincrona]). ConfigureAwait (false) '. –

+1

@ThanasisIoannidis: Sarebbe grandioso in teoria, ma in realtà manca "ConfigureAwait (false)" è di solito un errore. Quindi la documentazione sarebbe semplicemente sbagliata. –

Problemi correlati