C#异步文件复制中如何实现同步暂停以处理用户交互?
在C#中实现可暂停并等待用户输入的异步文件复制流程
需求背景
- 基于
async/await/Task<T>实现异步文件复制,全程不阻塞UI线程 - 当目标位置已存在待复制文件时,需在主线程显示模态对话框询问用户操作(覆盖/跳过/全部覆盖/全部跳过等)
- 多复制任务需逐个处理此类交互场景,避免弹窗混乱
现有方案分析
你提出的拆分Task、保存状态、遇到交互时终止当前Task并重启的思路是可行的,但存在状态管理繁琐、流程拆分碎片化的问题——每次重启都需要恢复到之前的执行节点,代码复杂度会随着流程分支增加而上升。
更优实现方案:异步等待用户输入模式
核心思路是不终止正在运行的Task,而是让Task暂停并异步等待用户的决策,同时确保UI操作在主线程执行。使用TaskCompletionSource<T>可以优雅地实现这种“等待外部输入”的异步逻辑,结合UI框架的线程调度器(如WPF的Dispatcher、WinForms的Control.Invoke)保证弹窗在主线程显示。
代码实现示例
首先定义必要的枚举和状态类:
public enum FileConflictAction { Overwrite, Skip, OverwriteAll, SkipAll } public class FileCopyState { public string SourcePath { get; set; } public string DestinationPath { get; set; } public long TotalBytesCopied { get; set; } public FileConflictAction? GlobalConflictAction { get; set; } }
主复制流程实现:
// 多任务场景下可通过队列逐个调用此方法 public async Task ExecuteSingleFileCopyAsync(FileCopyState state, IProgress<long> copyProgress) { // 如果已有全局决策,直接应用无需弹窗 if (state.GlobalConflictAction.HasValue) { await ProcessFileWithDecision(state, state.GlobalConflictAction.Value, copyProgress); return; } // 检查目标文件是否存在 if (File.Exists(state.DestinationPath)) { // 异步等待用户决策,确保弹窗在UI线程显示 var userAction = await AskUserForConflictResolution(state); // 记录全局决策(如果用户选择了"全部覆盖/跳过") if (userAction is FileConflictAction.OverwriteAll or FileConflictAction.SkipAll) { state.GlobalConflictAction = userAction; } await ProcessFileWithDecision(state, userAction, copyProgress); } else { await CopyFileToDestination(state, copyProgress); } } // 核心:异步等待用户输入的方法 private async Task<FileConflictAction> AskUserForConflictResolution(FileCopyState state) { var tcs = new TaskCompletionSource<FileConflictAction>(); // WPF环境下使用Dispatcher确保UI操作在主线程执行 Application.Current.Dispatcher.Invoke(() => { var dialog = new FileConflictDialog(state.SourcePath, state.DestinationPath); bool? dialogResult = dialog.ShowDialog(); tcs.SetResult(dialogResult == true ? dialog.SelectedAction : FileConflictAction.Skip); }); // WinForms环境下替换为Control.Invoke: // yourProgressWindow.Invoke(() => { ... }); return await tcs.Task; } // 实际执行文件复制的异步方法 private async Task CopyFileToDestination(FileCopyState state, IProgress<long> copyProgress) { const int bufferSize = 4096; using var sourceStream = new FileStream(state.SourcePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, true); using var destStream = new FileStream(state.DestinationPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize, true); byte[] buffer = new byte[bufferSize]; int bytesRead; while ((bytesRead = await sourceStream.ReadAsync(buffer)) > 0) { await destStream.WriteAsync(buffer.AsMemory(0, bytesRead)); state.TotalBytesCopied += bytesRead; copyProgress.Report(state.TotalBytesCopied); } } // 根据用户决策处理文件 private async Task ProcessFileWithDecision(FileCopyState state, FileConflictAction action, IProgress<long> copyProgress) { switch (action) { case FileConflictAction.Overwrite: case FileConflictAction.OverwriteAll: await CopyFileToDestination(state, copyProgress); break; case FileConflictAction.Skip: case FileConflictAction.SkipAll: // 跳过当前文件,可记录跳过日志 break; } }
方案优势
- 流程连续:无需拆分Task,整个复制流程在同一个Task内完成,状态管理更自然
- 异步等待:通过
TaskCompletionSource实现非阻塞等待,Task暂停但不终止,避免重复初始化流程 - 线程安全:通过UI框架的调度器确保弹窗在主线程显示,符合UI线程模型要求
- 扩展性强:轻松支持全局决策、多任务队列等需求,代码结构清晰易维护
多任务场景处理
如果是批量复制任务,只需将所有FileCopyState放入一个队列,然后逐个执行ExecuteSingleFileCopyAsync即可:
public async Task ExecuteBatchCopyAsync(List<FileCopyState> copyTasks, IProgress<long> totalProgress) { long totalBytes = copyTasks.Sum(t => new FileInfo(t.SourcePath).Length); long copiedBytes = 0; foreach (var task in copyTasks) { await ExecuteSingleFileCopyAsync(task, new Progress<long>(bytes => { copiedBytes = task.TotalBytesCopied; totalProgress.Report(copiedBytes * 100 / totalBytes); })); } }
内容的提问来源于stack exchange,提问作者Spook
相关产品推荐
相关产品推荐

