2010-03-19 8 views
8

Sto cercando di implementare un modello che ho letto dal blog di Don SymeAsync.Parallel o Array.Parallel.Map?

(http://blogs.msdn.com/dsyme/archive/2010/01/09/async-and-parallel-design-patterns-in-f-parallelizing-cpu-and-i-o-computations.aspx)

che suggerisce che ci sono opportunità di miglioramento enormi prestazioni da leveraging I/O asincrono. Attualmente sto cercando di prendere un pezzo di codice che "funziona" in un modo, usando Array.Parallel.Map, e vedere se riesco in qualche modo a ottenere lo stesso risultato usando Async.Parallel, ma davvero non capisco Async.Parallel, e non riesco a far funzionare nulla.

Ho un pezzo di codice (di seguito semplificato per illustrare il punto) che recupera correttamente una serie di dati per un cusip. (Una serie di prezzo, ad esempio)

let getStockData cusip = 
    let D = DataProvider() 
    let arr = D.GetPriceSeries(cusip) 
    return arr 

let data = Array.Parallel.map (fun x -> getStockData x) stockCusips 

Quindi questo approccio costrutti un array di array, facendo un collegamento via Internet al mio fornitore di dati per ogni stock (che potrebbe essere fino a 3000) e mi ritorna una serie di matrici (1 per azione, con una serie di prezzi per ciascuna). Ovviamente non capisco cosa succede sotto Array.Parallel.map, ma mi chiedo se questo è uno scenario in cui ci sono risorse sprecate sotto il cofano, e in realtà potrebbe essere più veloce usando l'I/O asincrono? Quindi, per verificarlo, ho provato a fare questa funzione usando asyncs, e penso che la funzione seguente segua lo schema nell'articolo di Don Syme usando gli URL, ma non verrà compilato con "let!".

let getStockDataAsync cusip = 
    async { let D = DataProvider() 
      let! arr = D.GetData(cusip) 
      return arr 
      } 

L'errore che ottengo è: 'let' Questa espressione è stata dovrebbe avere tipo Async < 'a> ma qui è di tipo obj

Si compila bene con 'lasciare' invece di, ma Avevo pensato che il punto era che hai bisogno del punto esclamativo perché il comando possa girare senza bloccare un thread.

Quindi la prima domanda è, cosa c'è di sbagliato con la mia sintassi sopra, in getStockDataAsync, e poi a un livello più alto, qualcuno può offrire qualche informazione aggiuntiva sull'I/O asincrono e se lo scenario che ho presentato trarrebbe beneficio da esso , rendendolo potenzialmente molto, molto più veloce di Array.Parallel.map? Grazie mille.

risposta

18

I flussi di lavoro asincroni F # consentono di implementare calcoli asincroni, tuttavia F fa una distinzione tra il calcolo normale e i calcoli asincroni . Questa differenza è tracciata dal sistema di tipi. Ad esempio un metodo che scarica la pagina Web ed è sincrono ha un tipo string -> string (prendendo URL e restituisce HTML), ma un metodo che fa la stessa cosa in modo asincrono ha un tipo string -> Async<string>. Nel blocco async, è possibile utilizzare let! per chiamare operazioni asincrone, ma tutti gli altri metodi (standard sincrono) devono essere chiamati utilizzando let. Ora, il problema con il tuo esempio è che l'operazione GetData è ordinaria metodo sincrono, quindi non è possibile richiamarla con let!.

Nel tipico F # scenario, se si vuole fare l'asincrona GetData membro, è necessario per la sua attuazione mediante un flusso di lavoro asincrono, così avrete anche bisogno di avvolgerlo nel blocco async. Ad un certo punto, si raggiungerà una posizione in cui è veramente necessario eseguire alcune operazioni primitive in modo asincrono (ad esempio, il download di dati da un sito Web). F # fornisce diverse operazioni asincrone primitive che è possibile chiamare dal blocco come let! come AsyncGetResponse (che è una versione asincrona del metodo GetResponse).Così, nel metodo GetData, potrai ad esempio scrivere qualcosa del genere:

let GetData (url:string) = async { 
    let req = WebRequest.Create(url) 
    let! rsp = req.AsyncGetResponse() 
    use stream = rsp.GetResponseStream() 
    use reader = new System.IO.StreamReader(stream) 
    let html = reader.AsyncReadToEnd() 
    return CalculateResult(html) } 

La sintesi è che è necessario individuare alcune operazioni asincrone primitivi (come in attesa per il server Web o per il file system) , utilizzare primarie operazioni asincrone in quel punto e avvolgere tutto il codice che utilizza queste operazioni nei blocchi async. Se non ci sono operazioni primitive che possono essere eseguite in modo asincrono, allora il tuo codice è vincolato alla CPU e puoi semplicemente usare Parallel.map.

Spero che questo ti aiuti a capire come funzionano i flussi di lavoro asincroni F #. Per ulteriori informazioni, è possibile ad esempio dare un'occhiata a Don Syme's blog post, serie su asynchronous programming by Robert Pickering, o il mio F# web cast.

+0

Grazie per la spiegazione dettagliata. E 'stato molto utile. – user297400

5

@Tomas ha già un'ottima risposta. Dico solo un paio di bit in più.

L'espressione per F # asyncs è denominare il metodo con un prefisso "Async" (AsyncFoo, non FooAsync, il secondo è un linguaggio già utilizzato da un'altra tecnologia .NET). Quindi le tue funzioni dovrebbero essere getStockData e asyncGetStockData.

All'interno di un flusso di lavoro asincrona, ogni volta che si utilizza let! invece di let o do! invece di do, la cosa a destra dovrebbe avere tipo Async<T> invece di T. Fondamentalmente è necessario un calcolo asincrono esistente per "andare asincroni" a questo punto del flusso di lavoro. Ogni Async<T> sarà di per sé un altro flusso di lavoro async{...} oppure un "primitivo" asincrono. I primitivi sono definiti nella libreria F # o creati nel codice utente tramite Async.FromBeginEnd o Async.FromContinuations che abilitano la definizione dei dettagli di basso livello dell'avvio di un calcolo, la registrazione di un callback I/O, il rilascio del thread e il riavvio del calcolo al richiamo . Quindi bisogna "scandagliare" fino in fondo asincrona fino ad arrivare ad una primitiva I/O veramente asincrona per ottenere tutti i benefici dell'I/O asincrono.

+0

anche questo aiuta ... grazie mille! – user297400