WPF Task下载文件:网络中断异常未触发及续传方法咨询
WPF大文件下载:异常捕获与续传实现方案
嘿,这个问题我之前做WPF大文件下载时也踩过坑,咱们一步步拆解解决:
一、为啥网络断开时没触发异常?
你遇到的情况其实挺典型的:很多网络流在连接意外断开时,不会直接抛出IOException,而是让strm.Read()返回0(表示流已结束)。如果你的代码只是判断“读取到字节数>0才继续”,就会直接退出任务,完全意识不到这是异常情况。
另外还有一种可能:你没正确处理Task里的异常。如果Task.Run中的代码抛出了异常,但你既没await这个Task,也没通过Task.Exception去捕获,那这个异常就会被“静默吞掉”,看起来就像代码直接退出了。
修复异常检测的代码示例
假设你原来的下载代码大概是这样:
await Task.Run(() => { using var client = new HttpClient(); using var response = client.GetStreamAsync(url).Result; using var fileStream = new FileStream(localPath, FileMode.Create); var buffer = new byte[8192]; int bytesRead; long totalBytes = response.Length; long downloadedBytes = 0; while ((bytesRead = response.Read(buffer, 0, buffer.Length)) > 0) { fileStream.Write(buffer, 0, bytesRead); downloadedBytes += bytesRead; progress.Report((float)downloadedBytes / totalBytes * 100); } });
那你需要做两个关键调整:
- 先确保HTTP请求本身成功,并且能获取到文件总大小;
- 当
bytesRead == 0但下载进度还没到100%时,手动抛出异常——这说明流提前结束了,肯定是网络出问题了:
await Task.Run(async () => { using var client = new HttpClient(); // 先获取响应头,再获取流,这样能提前拿到文件大小 using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); // 确保请求本身是成功的(比如不是404、500) long totalBytes = response.Content.Headers.ContentLength ?? -1; long downloadedBytes = 0; var buffer = new byte[8192]; using var responseStream = await response.Content.ReadAsStreamAsync(); using var fileStream = new FileStream(localPath, FileMode.Create); int bytesRead; while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length)) > 0) { await fileStream.WriteAsync(buffer, 0, bytesRead); downloadedBytes += bytesRead; if (totalBytes > 0) { progress.Report((float)downloadedBytes / totalBytes * 100); } } // 这里是关键:如果没下载完就返回0,说明异常中断了 if (totalBytes > 0 && downloadedBytes < totalBytes) { throw new IOException("网络连接意外中断,下载未完成"); } });
另外,一定要在调用的地方await这个Task,并加上try-catch,这样异常才能被捕获到:
try { await DownloadLargeFile(url, localPath, progress); } catch (Exception ex) { // 这里给用户提示,比如弹个MessageBox MessageBox.Show($"下载出错啦:{ex.Message}"); }
二、如何实现网络恢复后的续传?
续传的核心是利用HTTP的Range请求头——简单说就是告诉服务器:“我已经下载了X字节,你从X字节之后开始发就行”。具体步骤如下:
1. 记录已下载的字节数
先检查本地文件是否存在,存在的话就获取它的大小,这就是已经下载的字节数:
long startByte = 0; if (File.Exists(localPath)) { startByte = new FileInfo(localPath).Length; }
2. 发送Range请求
给HttpClient的请求添加Range头,指定从startByte开始下载:
using var client = new HttpClient(); var request = new HttpRequestMessage(HttpMethod.Get, url); // 设置Range:从startByte开始,到文件末尾(null表示末尾) request.Headers.Range = new RangeHeaderValue(startByte, null); using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
这里要注意:不是所有服务器都支持Range请求。如果服务器支持,会返回206 Partial Content状态码;如果不支持,会返回200 OK并发送整个文件,这时候你得考虑是覆盖本地文件还是提示用户无法续传。
3. 追加写入本地文件
打开文件时用FileMode.Append,把新下载的字节追加到文件末尾,而不是覆盖:
using var fileStream = new FileStream(localPath, FileMode.Append, FileAccess.Write, FileShare.None);
完整的续传代码示例
public async Task ResumeDownloadAsync(string url, string localPath, IProgress<float> progress, CancellationToken cancellationToken = default) { long startByte = 0; long totalBytes = 0; // 获取已下载的字节数 if (File.Exists(localPath)) { startByte = new FileInfo(localPath).Length; } using var client = new HttpClient(); var request = new HttpRequestMessage(HttpMethod.Get, url); // 如果已经下载过一部分,就发送Range请求 if (startByte > 0) { request.Headers.Range = new RangeHeaderValue(startByte, null); } using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); response.EnsureSuccessStatusCode(); // 获取文件总大小:续传时,服务器会在Content-Range头里返回完整文件大小 if (response.StatusCode == HttpStatusCode.PartialContent) { totalBytes = response.Content.Headers.ContentRange.Length ?? -1; } else { totalBytes = response.Content.Headers.ContentLength ?? -1; } // 如果拿不到总大小,就没法计算进度和判断是否下载完成 if (totalBytes == -1) { throw new InvalidOperationException("无法获取文件总大小,无法支持续传"); } var buffer = new byte[8192]; long downloadedBytes = startByte; using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); using var fileStream = new FileStream(localPath, FileMode.Append, FileAccess.Write, FileShare.None); int bytesRead; while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) { await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken); downloadedBytes += bytesRead; // 更新进度 progress.Report((float)downloadedBytes / totalBytes * 100); // 检查是否被取消 cancellationToken.ThrowIfCancellationRequested(); } // 最后检查是否真的下载完成 if (downloadedBytes < totalBytes) { throw new IOException("下载中断,未完成"); } }
额外小贴士
- 处理服务器不支持Range的情况:如果发送Range请求后返回200 OK,说明服务器不支持续传,你可以提示用户,或者直接重新下载整个文件;
- 加入
CancellationToken,方便用户随时取消下载; - 可以把已下载的字节数和文件URL存在本地配置里,避免每次都读取大文件的长度(毕竟大文件读长度也有点耗时);
- 网络恢复后,只要再次调用这个续传方法,它会自动检测已下载的部分,继续剩下的下载。
内容的提问来源于stack exchange,提问作者Saimon Frost




