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

C# WebClient异步下载取消、暂停恢复实现及取消报错问题咨询

问题解决与实现方案

取消功能报错修复

报错根因

  • 重复释放WebClient实例:你在DownloadFileCompleted事件中手动调用了client.Dispose(),而WebClient实例是包裹在using块中的,using块退出时会自动执行Dispose,重复释放会导致对象访问异常。
  • 取消后残留事件回调未处理:调用CancelAsync()后,尚未执行完的DownloadProgressChanged事件仍会触发,此时访问已释放的client的ResponseHeaders等属性会抛出ObjectDisposedException。
  • 取消操作抛出的异常未捕获:DownloadFileTaskAsync在被取消时会抛出TaskCanceledException,你没有捕获该异常导致程序直接崩溃。
  • 全局client变量悬空:using块执行完后全局client变量指向已释放的对象,再次点击停止按钮会访问无效对象。

修复代码要点

首先修改DownloadAsync方法:

private async Task DownloadAsync()
{
    client = new WebClient();
    try
    {
        client.DownloadFileCompleted += (s, e) => 
        {
            if (e.Cancelled)
            {
                lblStatus.Text = "Download Stopped";
                return;
            }
            if (e.Error != null)
            {
                lblStatus.Text = $"Download Error: {e.Error.Message}";
                return;
            }
            lblStatus.Text = "Download File Completed.";
        };
        client.DownloadProgressChanged += (s, e) => 
        {
            // 先判断是否已取消/释放,避免访问无效对象
            if (client == null || e.Cancelled) return;
            tracker.SetProgress(e.BytesReceived, e.TotalBytesToReceive);
            lblAmount.Text = SizeSuffix(e.BytesReceived) + "/" + SizeSuffix(e.TotalBytesToReceive);
            lblSpeed.Text = tracker.GetBytesPerSecondString();
            if (client.ResponseHeaders != null && client.ResponseHeaders["Content-Length"] != null)
            {
                myLong = Convert.ToInt64(client.ResponseHeaders["Content-Length"]);
            }
            progressBar1.Value = e.ProgressPercentage;
            lblDownloadProgress.Text = "%" + e.ProgressPercentage.ToString();
            lblDownloadProgress.Left = Math.Min(
                (int)(progressBar1.Left + e.ProgressPercentage / 100f * progressBar1.Width),
                progressBar1.Width - lblDownloadProgress.Width
            );
        };

        for (int i = 0; i < urls.Count; i++)
        {
            tracker.NewFile();
            // 捕获取消异常
            try
            {
                await client.DownloadFileTaskAsync(new Uri(urls[i]), @"d:\satImages\img" + i + ".gif");
            }
            catch (TaskCanceledException)
            {
                // 取消操作属于正常逻辑,不抛异常
                break;
            }
            catch (OperationCanceledException)
            {
                break;
            }
        }
    }
    finally
    {
        client.Dispose();
        client = null;
    }
}

然后修改停止按钮逻辑:

private void btnStop_Click(object sender, EventArgs e)
{
    if (client != null)
    {
        client.CancelAsync();
    }
}

暂停/恢复功能实现

WebClient本身不支持断点续传,需要改用HttpClient或HttpWebRequest实现,核心逻辑如下:

实现思路

  • 新增全局变量记录状态:bool isPaused、long downloadedBytes、string currentUrl、string currentFilePath
  • 暂停时:关闭当前请求,将isPaused置为true,记录当前已下载的字节数、当前下载的url和文件路径
  • 恢复时:发送带Range请求头的Http请求,指定从已下载的字节位置开始获取数据,将返回的流追加到本地已存在的文件末尾,继续更新进度
  • 执行续传前需要先判断目标服务器是否支持Range请求(响应头包含Accept-Ranges: bytes),不支持的服务器无法实现续传

核心代码示例

// 新增全局变量
private bool isPaused = false;
private long downloadedBytes = 0;
private string currentUrl;
private string currentFilePath;
private HttpClient httpClient = new HttpClient();
private Stream downloadStream;
private FileStream fileStream;

// 暂停按钮逻辑
private void btnPause_Click(object sender, EventArgs e)
{
    isPaused = true;
    downloadStream?.Dispose();
    fileStream?.Dispose();
    lblStatus.Text = "Download Paused";
}

// 恢复下载逻辑
private async void btnResume_Click(object sender, EventArgs e)
{
    if (!isPaused || string.IsNullOrEmpty(currentUrl)) return;
    isPaused = false;
    lblStatus.Text = "Download Resuming...";
    await ResumeDownloadAsync();
}

private async Task ResumeDownloadAsync()
{
    var fileInfo = new FileInfo(currentFilePath);
    if (fileInfo.Exists)
    {
        downloadedBytes = fileInfo.Length;
    }
    else
    {
        downloadedBytes = 0;
    }

    using (var request = new HttpRequestMessage(HttpMethod.Get, currentUrl))
    {
        // 添加上下文范围请求头
        request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(downloadedBytes, null);
        using (var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
        {
            if (!response.IsSuccessStatusCode)
            {
                lblStatus.Text = "Server does not support resume download";
                return;
            }
            var totalBytes = response.Content.Headers.ContentLength ?? 0 + downloadedBytes;
            downloadStream = await response.Content.ReadAsStreamAsync();
            fileStream = new FileStream(currentFilePath, FileMode.Append, FileAccess.Write, FileShare.None);

            var buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = await downloadStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
            {
                if (isPaused) break;
                await fileStream.WriteAsync(buffer, 0, bytesRead);
                downloadedBytes += bytesRead;
                // 更新进度UI
                var progress = (int)(downloadedBytes * 100 / totalBytes);
                progressBar1.Value = progress;
                lblDownloadProgress.Text = $"%{progress}";
                lblAmount.Text = $"{SizeSuffix(downloadedBytes)}/{SizeSuffix(totalBytes)}";
            }

            downloadStream.Dispose();
            fileStream.Dispose();
            if (!isPaused)
            {
                lblStatus.Text = "Download Completed";
                downloadedBytes = 0;
                currentUrl = null;
                currentFilePath = null;
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者Daniel Lip

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 03:24:08