如何基于HttpClient和HttpWebRequest实现文件下载暂停恢复按钮逻辑
实现下载暂停/恢复功能
已在Form1设计器中添加btnPauseResume按钮,需求如下:
- 按钮默认文本为“Pause”,点击后暂停当前文件下载,按钮文本切换为“Resume”
- 再次点击按钮时,从断点处恢复下载
解决方案实现步骤
1. 新增Form1成员变量
在Form1类中添加用于跟踪下载状态的变量:
public partial class Form1 : Form { // 原有变量保留,新增以下内容 private bool isPaused = false; private string currentDownloadUrl; private long downloadedBytes = 0; private CancellationTokenSource pauseTokenSource; // 构造函数及其他原有代码不变 }
2. 修改DownloadFile方法以支持断点续传
调整方法逻辑,支持从指定字节位置开始下载,并区分暂停和整体取消的场景:
private async Task DownloadFile(string docUrl, string path, long startByte = 0) { currentDownloadUrl = docUrl; downloadedBytes = startByte; var client = new HttpClient(); var filePath = Path.Combine(path, Path.GetFileNameWithoutExtension(docUrl)); downloadFilePath = filePath; Invoke((MethodInvoker)delegate { textBoxUrl.Text = docUrl; }); var progress = new Progress<float>(); var speedProgress = new Progress<Tuple<long, TimeSpan>>(); var sizeProgress = new Progress<long>(); progress.ProgressChanged += Progress_ProgressChanged; sizeProgress.ProgressChanged += SizeProgress_ProgressChanged; var fileMode = startByte == 0 ? FileMode.Create : FileMode.Append; using (downloadStream = new FileStream(filePath, fileMode, FileAccess.Write, FileShare.None)) { if (startByte > 0) { downloadStream.Seek(startByte, SeekOrigin.Begin); } try { pauseTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationTokenSource.Token); await client.DownloadDataAsync(docUrl, downloadStream, progress, speedProgress, sizeProgress, pauseTokenSource.Token, startByte); } catch (OperationCanceledException) { if (isPaused) return; if (File.Exists(downloadFilePath)) { File.Delete(downloadFilePath); } } finally { CloseFileStream(); } } }
3. 扩展HttpClientProgressExtensions支持断点续传
修改DownloadDataAsync方法,添加Range请求头实现断点续传:
public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress<float> progress = null, IProgress<Tuple<long, TimeSpan>> speedProgress = null, IProgress<long> sizeProgress = null, CancellationToken cancellationToken = default(CancellationToken), long startByte = 0) { using (var request = new HttpRequestMessage(HttpMethod.Get, requestUrl)) { if (startByte > 0) { request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(startByte, null); } using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) { if (startByte > 0 && response.StatusCode != System.Net.HttpStatusCode.PartialContent) { throw new InvalidOperationException("服务器不支持断点续传"); } var contentLength = response.Content.Headers.ContentLength; var totalContentLength = startByte + (contentLength ?? 0); contentSize = totalContentLength; using (var download = await response.Content.ReadAsStreamAsync(cancellationToken)) { if (progress is null || !contentLength.HasValue) { await download.CopyToAsync(destination, cancellationToken); return; } var watch = new System.Diagnostics.Stopwatch(); watch.Start(); var progressWrapper = new Progress<long>(bytesRead => { var totalBytes = startByte + bytesRead; var elapsed = watch.Elapsed; progress.Report(GetProgressPercentage(totalBytes, totalContentLength)); if (elapsed.TotalSeconds > 1) { var speed = (long)(bytesRead / elapsed.TotalSeconds); speedProgress?.Report(Tuple.Create(totalBytes, TimeSpan.FromSeconds(speed))); watch.Restart(); } sizeProgress?.Report(totalBytes); }); await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken); } } } float GetProgressPercentage(float totalBytes, float currentBytes) => (totalBytes / currentBytes) * 100f; }
4. 实现btnPauseResume按钮点击事件
添加按钮点击逻辑,切换暂停/恢复状态:
private async void btnPauseResume_Click(object sender, EventArgs e) { if (!isPaused) { isPaused = true; btnPauseResume.Text = "Resume"; pauseTokenSource?.Cancel(); } else { isPaused = false; btnPauseResume.Text = "Pause"; await DownloadFile(currentDownloadUrl, Path.GetDirectoryName(downloadFilePath), downloadedBytes); } }
5. 调整DownloadFiles方法的队列处理逻辑
确保暂停后不会终止整个下载队列:
private async Task DownloadFiles(string selectedFolder) { foreach (string link in links.Distinct()) { if (cancellationTokenSource.Token.IsCancellationRequested) break; if (!uniqueLinks.Contains(link)) { string filePath = Path.Combine(selectedFolder, Path.GetFileNameWithoutExtension(link)); if (!File.Exists(filePath)) { try { await DownloadFile(link, selectedFolder); anyDownloadsOccurred = true; } catch (OperationCanceledException) { if (isPaused) { links = links.Skip(links.IndexOf(link)).ToList(); break; } } } uniqueLinks.Add(link); } } if (!isPaused) { DownloadComplete(); } }
内容的提问来源于stack exchange,提问作者Daniel Lip
相关产品推荐
相关产品推荐

