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

C#异步文件夹复制时子进度条提前完成问题求助

问题根源
  1. 未等待文件复制任务完成:遍历文件时,每个CopyTo都通过Task.Run异步启动,但没有等待这些任务执行完毕,导致CopyFoldersRecursively方法很快走完循环,外层进度条直接触发完成回调。
  2. 进度条资源冲突:多个文件复制任务同时更新同一个progressBar2,进度值被频繁覆盖,看起来像是瞬间完成。
  3. 目标路径错误:当前代码把所有文件都复制到目标目录的根节点,没有保留原有的子目录结构,会导致文件重名覆盖,且路径逻辑错误。
修复方案

1. 修正目标路径逻辑

使用Path.Combine和路径替换来保留子目录结构,避免文件都堆在根目录。

2. 等待所有文件复制任务完成

逐个同步处理文件(保证单文件进度显示正常),同时计算整体文件总大小,累计更新整体进度。

3. 明确进度条职责

  • progressBar1:显示整体复制进度(基于总文件大小的累计进度)
  • progressBar2:显示当前正在复制的单个文件进度
修复后的完整代码
private async void button1_Click(object sender, EventArgs e)
{
    var source = txtSource.Text;
    var destination = txtDestination.Text;

    await Task.Run(() => CopyFoldersRecursively(source, destination, UpdateOverallProgress, UpdateCurrentFileProgress));
    progressBar1.BeginInvoke(() => progressBar1.Value = 100);
}

public void CopyFoldersRecursively(string sourcePath, string targetPath, Action<int> overallProgressCallback, Action<int> currentFileProgressCallback)
{
    try
    {
        // 创建所有子目录
        foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
        {
            var targetDir = dirPath.Replace(sourcePath, targetPath);
            Directory.CreateDirectory(targetDir);
        }

        // 获取所有要复制的文件并计算总大小
        var allFiles = Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories);
        long totalSize = allFiles.Sum(file => new FileInfo(file).Length);
        long copiedSize = 0;

        foreach (string newPath in allFiles)
        {
            var sourceFile = new FileInfo(newPath);
            // 修正目标路径,保留原目录结构
            var relativePath = sourceFile.FullName.Substring(sourcePath.Length);
            var targetFile = new FileInfo(Path.Combine(targetPath, relativePath.TrimStart(Path.DirectorySeparatorChar)));

            // 同步复制当前文件,确保单文件进度条显示正常
            sourceFile.CopyTo(targetFile, currentProgress =>
            {
                progressBar2.BeginInvoke(() =>
                {
                    progressBar2.Value = currentProgress;
                    lblPercent2.Text = $"{currentProgress}%";
                });
            });

            // 更新整体进度
            copiedSize += sourceFile.Length;
            int overallProgress = (int)((copiedSize / (float)totalSize) * 100);
            overallProgressCallback(overallProgress);
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show($"复制出错:{ex.Message}");
    }
}

public static class FileInfoExtension
{
    public static void CopyTo(this FileInfo file, FileInfo destination, Action<int> progressCallback)
    {
        const int bufferSize = 1024 * 1024;
        byte[] buffer = new byte[bufferSize], buffer2 = new byte[bufferSize];
        bool swap = false;
        int progress = 0, reportedProgress = 0, read = 0;
        long len = file.Length;
        float flen = len;
        Task writer = null;

        using (var source = file.OpenRead())
        using (var dest = destination.OpenWrite())
        {
            dest.SetLength(source.Length);
            for (long size = 0; size < len; size += read)
            {
                progress = (int)((size / flen) * 100);
                if (progress != reportedProgress)
                {
                    progressCallback(reportedProgress = progress);
                }

                read = source.Read(swap ? buffer : buffer2, 0, bufferSize);
                writer?.Wait();
                writer = dest.WriteAsync(swap ? buffer : buffer2, 0, read);
                swap = !swap;
            }
            writer?.Wait();
            progressCallback(100);
        }
    }
}

private void UpdateOverallProgress(int progress)
{
    progressBar1.BeginInvoke(() => progressBar1.Value = progress);
}

private void UpdateCurrentFileProgress(int progress)
{
    progressBar2.BeginInvoke(() =>
    {
        progressBar2.Value = progress;
        lblPercent2.Text = $"{progress}%";
    });
}
关键修改说明
  • UI线程优化:用async/await替代原有的Task.Run回调逻辑,让UI处理更简洁。
  • 路径修复:通过截取相对路径+Path.Combine,确保文件复制到对应子目录,保留原结构。
  • 整体进度计算:基于总文件大小累计已复制量,准确更新整体进度条。
  • 单文件进度同步:逐个处理文件,避免多个任务同时抢占进度条资源,保证进度显示准确。
  • 错误处理:添加UI线程的错误提示,避免异常直接抛出导致程序崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 16:28:17