You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WinForm多线程分块下载超时:Fiddler介入后恢复正常的原因排查

Why Fiddler Fixes Your Chunked Download Timeouts, and What's Wrong With Your Code

Let’s start with why Fiddler makes your app work—then dig into the bugs in your code that are causing timeouts when Fiddler isn’t running.

How Fiddler Fixes Your Problem

Fiddler acts as a local HTTP proxy, and it silently fixes several issues your code isn’t handling:

  • Bypasses .NET’s default connection limit: .NET’s ServicePointManager.DefaultConnectionLimit defaults to 2, meaning you can only have 2 concurrent requests to the same server. When you use more chunks than that, requests queue up and time out. Fiddler becomes the target of all your app’s requests, so .NET only limits connections to Fiddler (not the actual download server), and Fiddler manages connections to the server more efficiently.
  • Adds missing HTTP headers: Fiddler automatically adds standard headers like User-Agent, which many servers require to serve requests (some will block or throttle requests without it).
  • Relaxes timeouts: Fiddler’s internal proxy timeouts are more lenient than .NET’s default, and it may automatically retry failed requests that your code would let time out.

Critical Bugs in Your Code

Let’s fix the issues that are causing timeouts without Fiddler:

1. Closure Captures the Wrong Request Instance

In your for loop, you’re capturing the request variable directly in the thread lambda. Closures capture variable references, not values—so all threads end up using the last iteration’s request instance. This means multiple threads are trying to download the same chunk (or invalid ranges), leading to server errors or timeouts.

Fix: Create local variables inside the loop to capture the current iteration’s values:

for (int i = 1; i <= partCount; i++) {
    // Capture current iteration values in local variables
    int currentPart = i;
    long currentStart = startIndex;
    long currentPartSize = partSize;
    string currentPartName = $"part-{currentPart}.part";

    HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(txtUrl.Text);
    request.Method = "GET";
    // Add timeouts (adjust as needed)
    request.Timeout = 300000; // 5 minutes
    request.ReadWriteTimeout = 300000;
    // Add a valid User-Agent to avoid server blocks
    request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36";

    // Fix range calculation: AddRange's end is inclusive, so subtract 1
    if (currentPart == partCount) {
        currentPartSize = totalBytes - currentStart;
        request.AddRange(currentStart);
    } else {
        request.AddRange(currentStart, currentStart + currentPartSize - 1);
    }
    startIndex += currentPartSize;

    downloadThreads[currentPart - 1] = new Thread(() => {
        // Use local variables here, not loop variables
        byte[] buffer = new byte[8192]; // Larger buffer = better performance
        using (FileStream fs = new FileStream(currentPartName, FileMode.Create)) {
            ucDownloadStatus downloadStatus = new ucDownloadStatus();
            downloadStatus.PartName = currentPartName;
            AddControl(flowDownloadProgress, downloadStatus);

            try {
                using (WebResponse response = request.GetResponse()) {
                    using (var responseStream = response.GetResponseStream()) {
                        long totalReceived = 0;
                        int bytesRead;
                        while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0) {
                            fs.Write(buffer, 0, bytesRead);
                            totalReceived += bytesRead;
                            downloadStatus.Progress = (int)(totalReceived * 100 / currentPartSize);
                        }
                    }
                }
            } catch (WebException ex) {
                // Handle errors: show message, delete corrupted chunk
                MessageBox.Show($"Chunk {currentPartName} failed: {ex.Message}");
                if (File.Exists(currentPartName)) File.Delete(currentPartName);
            }
        }
    });
}

2. Low Connection Limit

As mentioned earlier, .NET defaults to 2 concurrent connections per server. Increase this in your form’s constructor to match your chunk count:

public Form1() {
    InitializeComponent();
    nmPartCount.Value = Environment.ProcessorCount;
    CheckForIllegalCrossThreadCalls = false;
    // Raise connection limit to match your chunk count (or higher)
    System.Net.ServicePointManager.DefaultConnectionLimit = (int)nmPartCount.Value;
}

3. Broken Chunk Merging

Your JoinParts method has two big issues:

  • It doesn’t close partStream, leading to file locks.
  • It merges chunks in random order (since GetFiles() doesn’t guarantee sorting).

Fix:

void JoinParts() {
    string outputFile = Path.GetFileName(txtUrl.Text);
    // Sort chunks by their part number
    string[] sortedParts = Directory.GetFiles(Application.StartupPath, "part-*.part")
        .OrderBy(file => int.Parse(Path.GetFileNameWithoutExtension(file).Split('-')[1]))
        .ToArray();

    using (FileStream outputStream = new FileStream(outputFile, FileMode.Create)) {
        foreach (string partFile in sortedParts) {
            using (FileStream partStream = new FileStream(partFile, FileMode.Open)) {
                partStream.CopyTo(outputStream);
            }
            // Delete the chunk after merging
            File.Delete(partFile);
        }
    }
    MessageBox.Show("Download completed successfully!");
}

4. Missing Error Handling

Your original code has no try/catch blocks, so you can’t see exactly why timeouts are happening. The added try/catch in the thread code will help you debug specific chunk failures.

Final Notes

Once you fix these issues, your app should work without Fiddler. Fiddler was masking these problems, but now you’ll have a robust, reliable chunked downloader.

内容的提问来源于stack exchange,提问作者Ibrahim Arac

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:56:57