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

使用HttpClient下载文件时,如何添加下载完成事件?

实现文件下载的进度与完成事件处理

核心思路

通过自定义事件和事件参数类,在单个文件下载完成(无论成功/失败)时触发完成事件,同时手动读取流来实现进度跟踪。

步骤1:定义事件参数类

用来传递下载过程中的关键信息:

public class FileDownloadCompletedEventArgs : EventArgs
{
    public string FileUrl { get; }
    public string LocalFilePath { get; }
    public bool IsSuccess { get; }
    public Exception? Exception { get; }

    public FileDownloadCompletedEventArgs(string fileUrl, string localFilePath, bool isSuccess, Exception? exception = null)
    {
        FileUrl = fileUrl;
        LocalFilePath = localFilePath;
        IsSuccess = isSuccess;
        Exception = exception;
    }
}

public class FileDownloadProgressEventArgs : EventArgs
{
    public string FileUrl { get; }
    public string LocalFilePath { get; }
    public long BytesDownloaded { get; }
    public long TotalBytes { get; }
    public double ProgressPercentage => TotalBytes > 0 ? (double)BytesDownloaded / TotalBytes * 100 : 0;

    public FileDownloadProgressEventArgs(string fileUrl, string localFilePath, long bytesDownloaded, long totalBytes)
    {
        FileUrl = fileUrl;
        LocalFilePath = localFilePath;
        BytesDownloaded = bytesDownloaded;
        TotalBytes = totalBytes;
    }
}

步骤2:在窗体类中声明事件

public event EventHandler<FileDownloadCompletedEventArgs>? FileDownloadCompleted;
public event EventHandler<FileDownloadProgressEventArgs>? FileDownloadProgressChanged;

步骤3:修改下载方法,触发事件

替换原有的Download方法,添加进度跟踪和完成事件触发逻辑:

private async Task Download()
{
    FilesDownoads downloads = new FilesDownloads(@"d:\testfiles");
    downloads.PrepareLinks();
    using var client = new HttpClient();

    for (int i = 0; i < files.Count; i++)
    {
        string fileUrl = files[i];
        string localPath = @"e:\files1\file" + i.ToString() + ".txt";
        bool isSuccess = false;
        Exception? ex = null;

        try
        {
            // 获取响应头先拿到文件总大小
            using var response = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead);
            response.EnsureSuccessStatusCode();
            long totalBytes = response.Content.Headers.ContentLength ?? -1;

            using var s = await response.Content.ReadAsStreamAsync();
            using var fs = new FileStream(localPath, FileMode.OpenOrCreate);
            
            byte[] buffer = new byte[8192];
            long bytesDownloaded = 0;
            int bytesRead;

            // 手动读写流,实时计算进度
            while ((bytesRead = await s.ReadAsync(buffer)) > 0)
            {
                await fs.WriteAsync(buffer.AsMemory(0, bytesRead));
                bytesDownloaded += bytesRead;

                // 触发进度更新事件
                FileDownloadProgressChanged?.Invoke(this, new FileDownloadProgressEventArgs(fileUrl, localPath, bytesDownloaded, totalBytes));
            }

            isSuccess = true;
        }
        catch (Exception exception)
        {
            ex = exception;
        }
        finally
        {
            // 无论成功失败,都触发完成事件
            FileDownloadCompleted?.Invoke(this, new FileDownloadCompletedEventArgs(fileUrl, localPath, isSuccess, ex));
        }
    }
}

步骤4:订阅并处理事件

在窗体初始化时订阅事件,编写完成后的处理逻辑(注意UI操作需切换到主线程):

public YourFormName()
{
    InitializeComponent();
    FileDownloadCompleted += OnFileDownloadCompleted;
    FileDownloadProgressChanged += OnFileDownloadProgressChanged;
}

private void OnFileDownloadCompleted(object? sender, FileDownloadCompletedEventArgs e)
{
    // UI操作必须回到主线程
    if (InvokeRequired)
    {
        Invoke(new Action(() => OnFileDownloadCompleted(sender, e)));
        return;
    }

    if (e.IsSuccess)
    {
        // 这里写文件下载成功后的操作,比如校验、解析、记录日志
        MessageBox.Show($"文件 {Path.GetFileName(e.LocalFilePath)} 下载完成");
    }
    else
    {
        MessageBox.Show($"下载失败:{e.FileUrl}\n错误信息:{e.Exception?.Message}");
    }
}

private void OnFileDownloadProgressChanged(object? sender, FileDownloadProgressEventArgs e)
{
    if (InvokeRequired)
    {
        Invoke(new Action(() => OnFileDownloadProgressChanged(sender, e)));
        return;
    }

    // 更新进度显示,比如进度条或标签
    labelProgress.Text = $"当前文件:{Path.GetFileName(e.LocalFilePath)}\n进度:{e.ProgressPercentage:F1}%";
}

关键说明

  • 完成事件:在finally块中触发,确保每个文件下载结束后都会通知,不管成功还是异常。
  • 进度跟踪:通过手动读写流的方式统计已下载字节数,实时触发进度事件;如果服务器返回的响应头没有Content-Length,进度百分比会显示0。
  • 线程安全:因为下载操作在后台线程执行,UI操作必须通过Invoke切换到主线程,避免跨线程操作异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 20:00:16