2014-01-16 22 views
6

我正在編寫一個圖書館,意圖在桌面(.Net 4.0及更高版本),手機(WP 7.5及更高版本)和Windows應用商店(Windows 8及更高版本)中使用它。如何實現便攜式HttpClient的進度報告

該庫能夠使用便攜式HttpClient庫從互聯網下載文件,並報告下載進度。

我在這裏和其他互聯網上搜索有關如何實施進度報告的文檔和代碼示例/指導原則,而這種搜索讓我無處可尋。

有沒有人有文章,文件,指導方針,代碼示例或任何幫助我實現這一目標?

回答

17

我寫了下面的代碼來實現進度報告。代碼支持我想要的所有平臺;但是,您需要參考以下的NuGet包:

  • Microsoft.Net.Http
  • Microsoft.Bcl.Async

下面是代碼:

public async Task DownloadFileAsync(string url, IProgress<double> progress, CancellationToken token) 
{ 
    var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); 

    if (!response.IsSuccessStatusCode) 
    { 
     throw new Exception(string.Format("The request returned with HTTP status code {0}", response.StatusCode)); 
    } 

    var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L; 
    var canReportProgress = total != -1 && progress != null; 

    using (var stream = await response.Content.ReadAsStreamAsync()) 
    { 
     var totalRead = 0L; 
     var buffer = new byte[4096]; 
     var isMoreToRead = true; 

     do 
     { 
      token.ThrowIfCancellationRequested(); 

      var read = await stream.ReadAsync(buffer, 0, buffer.Length, token); 

      if (read == 0) 
      { 
       isMoreToRead = false; 
      } 
      else 
      { 
       var data = new byte[read]; 
       buffer.ToList().CopyTo(0, data, 0, read); 

       // TODO: put here the code to write the file to disk 

       totalRead += read; 

       if (canReportProgress) 
       { 
        progress.Report((totalRead * 1d)/(total * 1d) * 100); 
       } 
      } 
     } while (isMoreToRead); 
    } 
} 

的使用它如此簡單:

var progress = new Microsoft.Progress<double>(); 
progress.ProgressChanged += (sender, value) => System.Console.Write("\r%{0:N0}", value); 

var cancellationToken = new CancellationTokenSource(); 

await DownloadFileAsync("http://www.dotpdn.com/files/Paint.NET.3.5.11.Install.zip", progress, cancellationToken.Token); 
+0

我知道這是一種necro-commenting,但我只想補充一點,我使用這種方法並遇到了計時問題,那裏有太多的進度輸出,它落後於下載的實際進度,導致在下載的文件中比所報告的進度要快得多。 (我最終使用秒錶來報告每秒鐘左右) – Dynde

+1

不知道是什麼原因造成了這種情況。我發現它在我的場景中運行良好;包括使用緩衝區大小4096字節。 – TheBlueSky

1

您可以指定HttpCompletionOption.ResponseHeadersRead,然後在從流中讀取時獲取流和報告進度。請參閱this similar question

+1

也許類似,但不是我的問題的答案,因爲它是。但感謝這個鏈接,它幫助我找到了答案。 – TheBlueSky

0

// TODO:把這個代碼寫到磁盤上

@TheBlueSky:對嗎?

public static async Task DownloadFileAsync(this HttpClient client, string url, string fileName, IProgress<double> progress, CancellationToken ct, int bufferSize = 4096, 
               bool overwrite = true) 
    { 
     #region Checking parameters 

     if (url.Length == 0) throw new ArgumentException("The parameter value can not be a zero-length string.", nameof(url)); 
     if (fileName == null) throw new ArgumentNullException(nameof(fileName), "File is null"); 
     if (fileName.Length == 0) throw new ArgumentException("The parameter value can not be a zero-length string.", nameof(fileName)); 

     #endregion 

     var uri  = new Uri(url, UriKind.Absolute); 
     var response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false); 
     if (!response.IsSuccessStatusCode) throw new Exception($"The request returned with HTTP status code {response.StatusCode}"); 

     var pathName   = Path.GetFullPath(fileName); 
     var total    = response.Content.Headers.ContentLength ?? -1L; 
     var canReportProgress = total != -1 && progress != null; 

     try 
     { 
      var stream  = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); 
      var totalRead = 0L; 
      var buffer  = new byte[bufferSize]; 
      var isMoreToRead = true; 
      do 
      { 
       ct.ThrowIfCancellationRequested(); 
       var read = await stream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false); 

       if (read == 0) 
       { 
        isMoreToRead = false; 
       } 
       else 
       { 
        var data = new byte[read]; 
        buffer.ToList().CopyTo(0, data, 0, read); 

        using (var fileStream = new FileStream(pathName, overwrite ? FileMode.Create : FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize, true)) 
        { 
         await fileStream.WriteAsync(data, 0, read, ct).ConfigureAwait(false); 
        } 

        totalRead += read; 
        if (canReportProgress) progress.Report(totalRead * 1d/(total * 1d) * 100); 
       } 
      } while (isMoreToRead); 
     } 
     catch (OutOfMemoryException e) 
     { 
      throw new OutOfMemoryException($"Set buffer size is large too. {e.Message}"); 
     } 
     catch (Exception e) 
     { 
      throw new Exception(e.Message); 
     } 
    }