2015-01-21 24 views
5

sto cercando di popolare la cache in modo asincronoAsync chiamata entro funzione sincrona

static ConcurrentDictionary<string, string[]> data = new ConcurrentDictionary<string, string[]>(); 

public static async Task<string[]> GetStuffAsync(string key) 
{ 
    return data.GetOrAdd(key, async (x) => { 
     return await LoadAsync(x); 
    }); 
} 

static async Task<string[]> LoadAsync(string key) {....} 

ma questo mi dà l'errore:

Cannot convert async lambda expression to delegate type 'System.Func'.

An async lambda expression may return void, Task or Task, none of which are convertible to 'System.Func'.

quanto ho capito che questo è perché GetOrAdd() non è asincrona. Come posso risolvere il problema?

Aggiornamento: LazyAsync suggerito nei commenti lavorerà nel mio esempio banale. Oppure, soluzione come questo (può sicuramente vivere con un certo overhead introduce):

public static async Task<string[]> GetStuffAsync(string key) 
{ 
    string[] d = null; 
    if (!data.ContainsKey(key)) 
     d = await LoadAsync(key); 
    return data.GetOrAdd(key, d); 
} 

La domanda allora diventa ha fatto Microsoft ha appena non hanno tempo per aggiornare tutte le interfacce per supportare asincrona o io sto cercando di fare qualcosa di profondamente sbagliato (e ConcurrentDictionary non dovrebbe avere GetOrAddAsync())?

+2

non è una risposta, ma forse è interessante per te: hai visto [AsyncLazy] (http://blogs.msdn.com/b/pfxteam/archive/2011/01/15/10116210.aspx)? – Default

+1

Beh, non puoi farlo usando l'interfaccia che hai. 'await' funziona solo quando si trova su livelli asincroni e ne manchi uno: il metodo' GetOrAdd' stesso. Avresti bisogno di una versione che sia asincrona e attenda l'intero metodo 'GetOrAdd' (nota come non lo stai facendo in questo momento - questo è un altro problema). Purtroppo, questo significa scrivere il tuo ConcurrentDictionary, che farà male. – Luaan

+0

Perché "ConcurrentDictionary" deve avere "GetOrAddAsync'? È una collezione sincrona in memoria. Non c'è motivo per avere un tale metodo. –

risposta

10

I metodi asincroni (o lambda) possono solo restituire void o Task o Task<T> ma il tuo lambda restituisce string[] e quindi il compilatore ti impedisce.

await la parola chiave è ottimizzata per continuare in modo sincrono quando l'attività è già completata. Quindi, un'opzione è quella di memorizzare l'attività stessa nel dizionario e non preoccuparti di attendere ancora e ancora l'attività completata.

private static ConcurrentDictionary<string, Task<string[]>> data = 
    new ConcurrentDictionary<string, Task<string[]>>(); 

public static Task<string[]> GetStuffAsync(string key) 
{ 
    return data.GetOrAdd(key, LoadAsync); 
} 

E quando lo fai

var item = await GetStuffAsync(...); 

prima volta sarà (a) aspettare che termina la Task cache --Ci dopo che continuerà in modo sincrono.

Dovrai pensare a cosa dovrebbe accadere quando LoadAsync non riesce. Perché stiamo memorizzando nella cache l'attività restituita da LoadAsync; se fallisce, memorizziamo stupidamente l'operazione fallita. Potrebbe essere necessario gestirlo.

+0

Potrebbe essere effettivamente possibile utilizzare questo approccio per costruire un wrapper attorno a 'ConcurrentDictionary' che esporrà il metodo asorbico' GetOrAddAsync' e gestirà gli errori all'interno di se stesso. Ci sarà ancora un sacco di lavoro complicato con i blocchi manuali, quindi potrebbe essere ancora una buona idea sincronizzare l'accesso usando uno dei modelli di sincronizzazione più vecchi. – Luaan

+1

invece di scrivere 'async (x) => aspetta LoadAsync (x)' puoi semplicemente scrivere 'x => LoadAsync (x)'. Sono lo stesso metodo. – Servy

+0

@Servy si, sembra ridondante anche per me. Grazie. Aggiornerò la mia risposta –

Problemi correlati