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

如何删除正在通过流下载的文件?遇文件占用异常求助

问题分析与修复方案

核心问题拆解

你的代码存在两个致命逻辑错误:

  1. CloseFileStream方法完全无效:你试图通过新建FileStream实例来"关闭"正在被下载任务占用的流——这完全是逻辑错误。新建的流和下载用的流是两个独立对象,就算创建成功,你关闭的也是新流,和原下载流毫无关系。而且因为下载流用了FileShare.None,新建流会直接触发文件占用异常,所以代码直接跳转到catch块。
  2. OnFormClosed执行顺序颠倒:你先调用无效的关流方法,才去取消下载任务,此时下载任务仍在运行,文件流被牢牢占用,后续删除文件必然报错。

修复步骤与代码示例

1. 调整资源引用逻辑

把下载用的FileStream、下载任务、取消令牌都改为类级别的字段,方便在窗体关闭时直接操作原资源:

private FileStream _downloadFileStream;
private CancellationTokenSource _cancellationTokenSource;
private string _downloadFilePath;
private Task _downloadTask; // 保存下载任务引用,用于等待任务结束

2. 修改DownloadFile方法,规范流管理

移除无效的CloseFileStream调用,直接操作原流:

private async Task DownloadFile(string docUrl, string path)
{
    var client = new HttpClient();
    _downloadFilePath = Path.Combine(path, Path.GetFileNameWithoutExtension(docUrl));

    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;

    // 将流引用保存到类字段
    _downloadFileStream = new FileStream(_downloadFilePath, FileMode.Create, FileAccess.Write, FileShare.None);
    try
    {
        await client.DownloadDataAsync(docUrl, _downloadFileStream, progress, speedProgress, sizeProgress, _cancellationTokenSource.Token);
    }
    finally
    {
        // 直接释放原流,Dispose会自动调用Close,是规范的资源释放方式
        _downloadFileStream?.Dispose();
        _downloadFileStream = null;

        if (isCancel)
        {
            if (File.Exists(_downloadFilePath))
            {
                File.Delete(_downloadFilePath);
            }
        }
    }
}

3. 修正OnFormClosed执行顺序

先取消任务、等待任务结束,再清理资源、删除文件:

protected override async void OnFormClosed(FormClosedEventArgs e)
{
    base.OnFormClosed(e);

    // 第一步:取消下载任务
    _cancellationTokenSource?.Cancel();
    
    // 第二步:等待下载任务彻底完成,确保流被释放
    if (_downloadTask != null)
    {
        try
        {
            await _downloadTask;
        }
        catch (OperationCanceledException)
        {
            // 任务被取消是预期情况,无需处理
        }
        catch (Exception ex)
        {
            Console.WriteLine($"下载任务异常:{ex.Message}");
        }
    }

    // 第三步:双重保险,确保流被释放
    _downloadFileStream?.Dispose();
    _downloadFileStream = null;

    // 第四步:删除临时文件(如果需要)
    if (File.Exists(_downloadFilePath))
    {
        try
        {
            File.Delete(_downloadFilePath);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"删除文件失败:{ex.Message}");
        }
    }

    // 清理取消令牌
    _cancellationTokenSource?.Dispose();
    _cancellationTokenSource = null;
}

4. 启动下载时保存任务引用

在触发下载的按钮点击事件中,要保存任务引用:

private void btnDownload_Click(object sender, EventArgs e)
{
    _cancellationTokenSource = new CancellationTokenSource();
    _downloadTask = DownloadFile("你的下载URL", "保存路径");
}

额外注意事项

  • 避免手动调用Close(),优先用Dispose()或using语句——Dispose()会自动处理流的关闭和资源释放,是.NET规范的资源管理方式。
  • async void仅用于UI事件处理方法(如OnFormClosed),其他场景尽量用async Task。
  • 下载时使用FileShare.None是合理的,但要确保同一时间只有一个流操作目标文件,避免锁冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 14:23:12