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

