2012-11-09 30 views
5

Sto tentando di avviare un'attività asincrona (su .NET 4.5) che scarica il contenuto della pagina Web, ma in qualche modo questa attività non termina mai.L'attività asincrona non termina

La mia classe PageDownloader:

using System.Net; 
using System.Text; 
using System.IO; 
using System.Net.Http; 
using System.Threading.Tasks; 
using System; 

namespace ParserConsole.WebClient 
{ 
public class PageDownloader 
{ 
    private System.Net.Http.HttpClient _client; 

    public PageDownloader() 
     : this(Encoding.UTF8) { } 

    private Encoding _encoding; 

    public PageDownloader(Encoding encoding) 
    { 
     _encoding = encoding; 
     _client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10)}; 
    } 

    private HttpRequestMessage _request; 
    private HttpResponseMessage _response; 
    private string _responseString; 

    public string GetPageData(string link) 
    { 
     _request = new HttpRequestMessage(HttpMethod.Get, link); 
     _request.Headers.Add("User-Agent", "Chrome/21.0.1180.89"); 
     _request.Headers.Add("Accept", "text/html"); 


     GetResponse().Wait(); 
     GetStringFromResponse().Wait(); 
     return _responseString;    
    } 

    private async Task<HttpResponseMessage> GetResponse() { 
     return _response = await _client.GetAsync(_request.RequestUri); 
    } 

    private async Task<string> GetStringFromResponse() { 
     return _responseString = await _response.Content.ReadAsStringAsync(); 
    } 

} 
} 

inizio a pagina scaricando chiamando

new PageDownloader().GetPageData(url); 

Quando sto cercando di eseguire il debug del codice, tutto è bene fino GetResponse().Wait(). Ma in qualche modo l'attività GetResponse() non termina mai: il punto di interruzione nella riga successiva non viene mai raggiunto. Non ottengo eccezioni, l'applicazione continua a funzionare. Eventuali suggerimenti?

risposta

9

Questa è una condizione di deadlock standard che si ottiene quando si avvia un'operazione async e si blocca sull'attività restituita.

Here è un post sul blog che tratta l'argomento.

In sostanza, la chiamata await assicura che i fili prosecuzione esso in su del compito verrà eseguito nel contesto eri originariamente in (che è molto utile), ma perché si sta chiamando Wait in quello stesso contesto, sta bloccando, così la continuazione non funziona mai e quella continuazione deve essere eseguita per l'attesa alla fine. Stallo classico.

Per quanto riguarda la correzione; di solito significa che non dovresti fare un'attesa di blocco sull'operazione asincrona; è contrario al design dell'intero sistema. Dovresti "asincrizzare tutto". In questo caso significherebbe che lo GetPageData dovrebbe restituire uno Task<string> piuttosto che uno string, e invece di attendere le altre operazioni che restituiscono un'attività, è necessario eseguire await.

Ora, detto questo, ci sono modi di fare un'attesa di blocco sulle operazioni asincrone senza deadlock. Mentre può essere fatto, sconfigge onestamente lo scopo di usare async/attendere in primo luogo. Il vantaggio principale dell'uso di quel sistema è che il contesto principale non è bloccato; quando blocchi su di esso l'intero vantaggio scompare, e potresti anche semplicemente usare il codice di blocco fino in fondo. async/await è davvero più di un paradigma tutto-o-niente.

Ecco come vorrei strutturare quella classe:

public class PageDownloader 
{ 
    private System.Net.Http.HttpClient _client; 
    private Encoding _encoding; 

    public PageDownloader() 
     : this(Encoding.UTF8) { } 

    public PageDownloader(Encoding encoding) 
    { 
     _encoding = encoding; 
     _client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) }; 
    } 

    public async Task<string> GetPageData(string link) 
    { 
     HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, link); 
     request.Headers.Add("User-Agent", "Chrome/21.0.1180.89"); 
     request.Headers.Add("Accept", "text/html"); 

     HttpResponseMessage response = await _client.GetAsync(request.RequestUri); 

     return await response.Content.ReadAsStringAsync(); ; 
    } 
} 
+0

Grazie per la buona risposta. Puoi anche spiegare come devo gestire la risposta nel codice chiamante? –

+0

@YuriyPogrebnyak Ero già in fase di modifica. – Servy

1

Perché non solo fare questo se si vuole avere una funzione simile.

public string GetPageData(string link) 
{ 
    _request = new HttpRequestMessage(HttpMethod.Get, link); 
    _request.Headers.Add("User-Agent", "Chrome/21.0.1180.89"); 
    _request.Headers.Add("Accept", "text/html"); 


    var readTask = _client.GetStringAsync(link); 
    readTask.Wait(); 
    return readTask.Result; 
} 

Sarebbe meglio tornare Task tutta la strada e gestire con async/aspettano nel codice chiamante.

public Task<string> GetPageData(string link) 
{ 
    _request = new HttpRequestMessage(HttpMethod.Get, link); 
    _request.Headers.Add("User-Agent", "Chrome/21.0.1180.89"); 
    _request.Headers.Add("Accept", "text/html"); 


    return _client.GetStringAsync(link); 
} 
+0

In che modo? Non sto iniziando un asincrono/attendo prima della chiamata di attesa. Sto usando una chiamata di attesa standard sull'attività. Forse mi manca qualcosa, ma sembra quasi identico agli esempi SDK di utilizzare il metodo Wait. –

+0

Sì, errore mio, è corretto. Suppongo che tu possa semplicemente usare la chiamata di blocco a quel punto però. Inoltre, i blocchi 'Result', quindi non c'è bisogno (anche se non ci sono problemi) di chiamare' Wait' prima. – Servy

+0

Nessun problema.Buon punto sul risultato, l'ho dimenticato. –