2016-04-01 12 views
15

Ho il seguente metodo:Come si chiama un metodo asincrono da un metodo non async?

public string RetrieveHolidayDatesFromSource() { 
     var result = this.RetrieveHolidayDatesFromSourceAsync(); 
     /** Do stuff **/ 
     var returnedResult = this.TransformResults(result.Result); /** Where result gets used **/ 
     return returnedResult; 
    } 


    private async Task<string> RetrieveHolidayDatesFromSourceAsync() { 
     using (var httpClient = new HttpClient()) { 
      var json = await httpClient.GetStringAsync(SourceURI); 
      return json; 
     } 
    } 

È possibile che questo non funziona e sembra non prodotto risultati in modo corretto. Non sono sicuro di dove mi manca una dichiarazione per forzare l'attesa di un risultato? Voglio il metodo RetrieveHolidayDatesFromSource() per restituire una stringa.

Quanto segue funziona bene ma è sincrono e credo che possa essere migliorato? Si noti che il sotto è sincrono in cui vorrei passare ad Asincrono ma non riesco a girarmi intorno per qualche motivo.

public string RetrieveHolidayDatesFromSource() { 
     var result = this.RetrieveHolidayDatesFromSourceAsync(); 
     /** Do Stuff **/ 

     var returnedResult = this.TransformResults(result); /** This is where Result is actually used**/ 
     return returnedResult; 
    } 


    private string RetrieveHolidayDatesFromSourceAsync() { 
     using (var httpClient = new HttpClient()) { 
      var json = httpClient.GetStringAsync(SourceURI); 
      return json.Result; 
     } 
    } 

Mi manca qualcosa?

Nota: per qualche motivo, quando si interrompe il metodo Async sopra riportato, quando arriva alla riga "var json = attende httpClient.GetStringAsync (SourceURI)" si esce dal breakpoint e non posso tornare indietro il metodo.

risposta

23

Mi manca qualcosa?

Sì. Il codice asincrono - per sua natura - implica che il thread corrente non viene utilizzato mentre l'operazione è in corso. Il codice sincrono - per sua natura - implica che il thread corrente sia bloccato mentre l'operazione è in corso. Ecco perché chiamare il codice asincrono dal codice sincrono letteralmente non ha nemmeno senso. Infatti, come descrivo sul mio blog, a naive approach (using Result/Wait) can easily result in deadlocks.

La prima cosa da considerare è: dovrebbe mia API essere sincrono o asincrono? Se si tratta di I/O (come in questo esempio), è should be asynchronous. Quindi, questo sarebbe un design più appropriato:

public async Task<string> RetrieveHolidayDatesFromSourceAsync() { 
    var result = await this.DoRetrieveHolidayDatesFromSourceAsync(); 
    /** Do stuff **/ 
    var returnedResult = this.TransformResults(result); /** Where result gets used **/ 
    return returnedResult; 
} 

Come ho descritto nel mio async best practices article, si dovrebbe andare "asincrona fino in fondo". Se non lo fai, non otterrai comunque alcun beneficio asincrono, quindi perché preoccuparsi?

Ma diciamo che siete interessati a finalmente andare asincrona, ma in questo momento non si può cambiare tutto , si vuole solo cambiare parte della vostra applicazione. Questa è una situazione abbastanza comune.

In tal caso, l'approccio corretto è esporre entrambe le API sincrone e asincrone. Alla fine, dopo aver aggiornato l'altro codice, è possibile rimuovere le API sincrone.Esplora una varietà di opzioni per questo tipo di scenario nel mio article on brownfield async development; il mio preferito è il "bool parametro di hack", che sarebbe simile a questa:

public string RetrieveHolidayDatesFromSource() { 
    return this.DoRetrieveHolidayDatesFromSourceAsync(sync: true).GetAwaiter().GetResult(); 
} 

public Task<string> RetrieveHolidayDatesFromSourceAsync() { 
    return this.DoRetrieveHolidayDatesFromSourceAsync(sync: false); 
} 

private async Task<string> DoRetrieveHolidayDatesFromSourceAsync(bool sync) { 
    var result = await this.GetHolidayDatesAsync(sync); 
    /** Do stuff **/ 
    var returnedResult = this.TransformResults(result); 
    return returnedResult; 
} 

private async Task<string> GetHolidayDatesAsync(bool sync) { 
    using (var client = new WebClient()) { 
    return sync 
     ? client.DownloadString(SourceURI) 
     : await client.DownloadStringTaskAsync(SourceURI); 
    } 
} 

Questo approccio evita la duplicazione del codice ed evita anche eventuali problemi deadlock o rientranza comuni con altre soluzioni antipattern "sync-over-asincrone".

Nota che il codice risultante verrà trattato come un "passaggio intermedio" nel percorso di un'API correttamente asincrona. In particolare, il codice interno doveva tornare su WebClient (che supporta sia la sincronizzazione che l'asincrono) anziché il preferito HttpClient (che supporta solo asincrono). Una volta che tutto il codice chiamante è stato modificato per usare RetrieveHolidayDatesFromSourceAsync e non RetrieveHolidayDatesFromSource, allora lo rivederei e rimuovere tutto il debito tecnologico, cambiandolo per usare HttpClient e essere solo asincrono.

+1

È 'public Task RetrieveHolidayDatesFromSourceAsync()' manca un 'await'? –

+3

@NickWeaver: No. Non è 'async', quindi non può usare' await'. –

+0

Capisco. Mi chiedevo poiché il nome del metodo indica "Async" alla fine. Perché non è impostato su asincrono? –

2
public string RetrieveHolidayDatesFromSource() { 
    var result = this.RetrieveHolidayDatesFromSourceAsync().Result; 
    /** Do stuff **/ 
    var returnedResult = this.TransformResults(result.Result); /** Where result gets used **/ 
    return returnedResult; 
} 

Se si aggiunge .Result alla chiamata asincrona, eseguirà e attendere il risultato di arrivare, costringendolo a essere sincroni

UPDATE:

private static string stringTest() 
{ 
    return getStringAsync().Result; 
} 

private static async Task<string> getStringAsync() 
{ 
    return await Task.FromResult<string>("Hello"); 
} 
static void Main(string[] args) 
{ 
    Console.WriteLine(stringTest()); 

} 

Per rispondere al commento: funziona senza problemi.

+1

Non penso che funzioni, poiché ho già il risultato "Risultato.Risultato" nel metodo. Avere questo.RetrieveHolidayDatesFromSourceAsync(). Il risultato cambierà il risultato in un oggetto non task che causerà un errore in "var returnedResult = this.TransformResults (result.Result)". –

+0

Ho aggiornato la mia risposta per mostrarvi codice funzionante. Puoi vedere getStringAsync è un metodo asincrono chiamato in un metodo sincronizzato (stringTest()), I Ottieni l'output desiderato senza errori. Qual è l'errore che stai ottenendo? –

+4

"Funziona senza problemi":) ... potresti essere un po 'più gentile e spiegare come gestire il deadlock risultante quando effettivamente utilizzato al di fuori delle app della console. –

Problemi correlati