首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

C# WebClient 並行下載中途失效:根因分析與斷點續傳重構方案

     編輯:程式設計綜合問答

現象複現

`Parallel.For` 併發 `WebClient.DownloadFile`,開始正常,運行一段時間後大量失敗,無異常信息。

核心根因

  1. WebClient 非線程安全:每次循環 `new WebClient()` 雖隔離實例,但底層 `ServicePointManager` 連接池默認限制 **每宿主 2 個並發連接**(HTTP/1.1),大量併發導致排隊、超時、連接重置。
  2. 無超時控制:默認 100 秒,網絡波動時阻塞線程池。
  3. 無重試/斷點續傳:任何中斷直接導致文件損壞,下次重新下載覆蓋。

重構方案:HttpClient + Polly + Range 斷點續傳

// 1. 靜態 HttpClient(單例模式)
private static readonly HttpClient _client = new HttpClient(new HttpClientHandler {
    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
}) { Timeout = TimeSpan.FromMinutes(5) };

// 2. Polly 重試策略
private static readonly AsyncRetryPolicy _retryPolicy = Policy
    .Handle()
    .Or()
    .WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)));

// 3. 斷點續傳核心邏輯
public async Task DownloadWithResumeAsync(string url, string destPath, CancellationToken ct) {
    long existingLength = 0;
    if (File.Exists(destPath)) existingLength = new FileInfo(destPath).Length;

    using (var request = new HttpRequestMessage(HttpMethod.Get, url)) {
        if (existingLength > 0)
            request.Headers.Range = new RangeHeaderValue(existingLength, null);

        using (var response = await _retryPolicy.ExecuteAsync(() => _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct))) {
            response.EnsureSuccessStatusCode();
            using (var fs = new FileStream(destPath, FileMode.Append, FileAccess.Write, FileShare.None, 8192, true))
            using (var stream = await response.Content.ReadAsStreamAsync()) {
                await stream.CopyToAsync(fs, 8192, ct);
            }
        }
    }
}

// 4. 受控併發下載(SemaphoreSlim 限制並發數)
var semaphore = new SemaphoreSlim(Environment.ProcessorCount * 2);
var tasks = urls.Select(async url => {
    await semaphore.WaitAsync();
    try { await DownloadWithResumeAsync(url, destPath, ct); }
    finally { semaphore.Release(); }
});
await Task.WhenAll(tasks);

關鍵改進點

  • HttpClient 單例:復用連接池,避免 Socket 耗盡。
  • SemaphoreSlim 限流:精確控制併發數,配合服務端限制。
  • Range 請求:支持斷點續傳,網絡中斷不重頭下載。
  • Polly 指數退避重試:自動應對瞬時故障。
  • 異步流 CopyToAsync:非阻塞,高吞吐。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved