7

Ho creato un tipo di reso webapi FileResult : IHttpActionResult per le mie chiamate api. FileResult scarica un file da un altro url e quindi restituisce il flusso al client.HttpClient nell'utilizzo dell'istruzione causa Attività annullata

Inizialmente il mio codice ha avuto una dichiarazione using come di seguito:

public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken) 
{ 
    try 
    { 
     HttpResponseMessage response; 
     using (var httpClient = new HttpClient()) 
     { 

      response = new HttpResponseMessage(HttpStatusCode.OK) 
      { 
       Content = new System.Net.Http.StreamContent(
            await httpClient.GetStreamAsync(this.filePath)) 
      }; 
     } 
     return response; 
    } 
    catch (WebException exception) 
    {...} 
} 

Tuttavia, questo potrebbe causare un modo intermittente TaskCanceledException. So che se HttpClient viene eliminato prima che la chiamata asincrona sia terminata, lo stato della Task cambierà in annullato. Tuttavia, poiché utilizzo uno , attendo in: Content = new System.Net.Http.StreamContent(await httpClient.GetStreamAsync(this.filePath)) che dovrebbe impedire a HttpClient di essere eliminato al termine del completamento dell'attività.

Perché l'attività viene annullata? Non è a causa di un timeout poiché ciò è accaduto per le richieste più piccole e non si verifica sempre in caso di richieste di grandi dimensioni.

Quando ho tolto il using comunicato il codice ha funzionato correttamente:

public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken) 
{ 
    try 
    { 
     HttpResponseMessage response; 
     var httpClient = new HttpClient(); 

     response = new HttpResponseMessage(HttpStatusCode.OK) 
     { 
      Content = new System.Net.Http.StreamContent(
           await httpClient.GetStreamAsync(this.filePath)) 
     }; 
     return response; 
    } 
    catch (WebException exception) 
    {...} 
} 

Qualsiasi idea del perché l'uso ha causato il problema?

+0

Hai provato a utilizzare un debugger? Controllandolo passo dopo passo. – kevintjuh93

+0

Sì, ho. Non è di grande aiuto in quanto l'eccezione non viene lanciata qui, ma in una posizione completamente diversa. Si verifica nella pipeline Owin che sto usando dove è l'autenticazione e attende la prossima richiesta. – Rafi

+0

Il 'TaskCanceledException' ha un'eccezione interna? –

risposta

9

so che se il HttpClient è disposto prima che la chiamata è terminata asincrono stato del Task cambierà in annullata. Tuttavia poiché utilizzo Attesa in: Content = new System.Net.Http.StreamContent (attendo httpClient.GetStreamAsync (this.filePath)) che dovrebbe impedire a HttpClient di essere eliminato nel mezzo del completamento dell'attività.

Ma che cosa fa questa attività ? Ottiene lo stream. Quindi, il tuo codice finisce con un Stream che può essere letto o meno durante la chiusura dello HttpClient.

HttpClient è specificamente progettato per il riutilizzo (e uso simultaneo), quindi vi consiglio rimuovere la using completamente e spostando la dichiarazione HttpClient di un membro static classe. Ma se vuoi chiudere e riaprire i client, dovresti riuscire a farlo funzionare allo leggendo lo streaming interamente in memoria prima di chiudere lo HttpClient.

4

Ho riscontrato un problema simile con le eccezioni Attività annullate. Se si tenta la cattura AggregateException o avere una cattura tutti Exception blocco sotto il tuo WebException, si può anche scoprire che lo cattura, con una sola eccezione, con la voce che indica "Un compito è stata annullata"

ho fatto qualche indagine e ha scoperto che il AggregateException è abbastanza fuorviante come descritto in vari thread;

Setting HttpClient to a too short timeout crashes process

How can I tell when HttpClient has timed out?

Bug in httpclientgetasync should throw webexception not taskcanceledexception

ho finito per cambiare il mio codice per impostare un timeout esplicito (dove asyncTimeoutInMins viene letta dal file app.config);

 string jsonResponse = string.Empty; 
     try 
     { 
      using (HttpClient httpClient = new HttpClient()) 
      { 
       httpClient.BaseAddress = new Uri(Properties.Settings.Default.MyWebService); 
       httpClient.DefaultRequestHeaders.Accept.Clear(); 
       httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 
       httpClient.Timeout = new TimeSpan(0, asyncTimeoutInMins, 0); 

       HttpResponseMessage response; 

       response = await httpClient.GetAsync("/myservice/resource"); 

       // Check the response StatusCode 
       if (response.IsSuccessStatusCode) 
       { 
        // Read the content of the response into a string 
        jsonResponse = await response.Content.ReadAsStringAsync(); 
       } 
       else if (response.StatusCode == HttpStatusCode.Forbidden) 
       { 
        jsonResponse = await response.Content.ReadAsStringAsync(); 

        Logger.Instance.Warning(new HttpRequestException(string.Format("The response StatusCode was {0} - {1}", response.StatusCode.ToString(), jsonResponse))); 

        Environment.Exit((int)ExitCodes.Unauthorised); 
       } 
       else 
       { 
        jsonResponse = await response.Content.ReadAsStringAsync(); 

        Logger.Instance.Warning(new HttpRequestException(string.Format("The response StatusCode was {0} - {1}", response.StatusCode.ToString(), jsonResponse))); 

        Environment.Exit((int)ExitCodes.ApplicationError); 
       } 
      } 

     } 
     catch (HttpRequestException reqEx) 
     { 
      Logger.Instance.Error(reqEx); 

      Console.WriteLine("HttpRequestException : {0}", reqEx.InnerException.Message); 

      Environment.Exit((int)ExitCodes.ApplicationError); 
     } 
     catch (Exception ex) 
     { 
      Logger.Instance.Error(ex); 

      throw; 
     } 

     return jsonResponse; 
+0

Come menzionato nel commento sopra, l'eccezione non viene catturata lì ma nel livello di autenticazione logica. – Rafi

Problemi correlati