基于CancellationToken实现异步文件搜索的问题求助
问题解决思路与代码修正
一、CancellationTokenSource 取消报错问题
你之前的代码在 finally 里直接释放了 _tokenSource,导致下次调用 SearchAsync 时,旧的令牌源已经被释放,调用 Cancel() 就会报错。正确做法是:每次启动新搜索前,先保存旧令牌源,创建新的后再取消旧任务,等旧任务结束后再释放旧令牌源。
二、Async/Await 规范使用
AddFileAsync改用Dispatcher.InvokeAsync替代BeginInvoke,它直接返回Task,更贴合异步语法。FindFiles里提前检查取消令牌,避免做无用的文件添加操作。- 确保所有异步操作都传递取消令牌,包括
Task.Run,让任务未启动时也能响应取消。
修正后的完整代码
搜索函数
private CancellationTokenSource _tokenSource; // 如果是事件触发,用async void没问题;否则建议改成async Task public async void SearchAsync() { // 保存旧令牌源,创建新的 var oldTokenSource = _tokenSource; _tokenSource = new CancellationTokenSource(); var currentToken = _tokenSource.Token; // 取消并清理旧任务 if (oldTokenSource != null) { try { oldTokenSource.Cancel(); // 等待旧任务结束,避免资源竞争 await Task.WhenAny(oldTokenSource.Token.WaitHandle.WaitOneAsync()); } catch { } // 忽略取消过程中的异常 finally { oldTokenSource.Dispose(); } } // 清空集合与计数(UI线程操作,直接调用即可) Files.Clear(); FileCount = 0; try { // 把取消令牌传入Task.Run,确保任务未启动时也能响应取消 await Task.Run(() => FindFiles(currentToken), currentToken); } catch (OperationCanceledException) { MessageBox.Show("搜索已取消", "提示"); } finally { // 只有当前令牌源还是我们创建的这个,才释放(防止被新的搜索覆盖) if (_tokenSource == oldTokenSource) { _tokenSource.Dispose(); _tokenSource = null; } } }
查找文件函数
private async Task FindFiles(CancellationToken token) { foreach (string filePath in Directory.EnumerateFiles(MainPath, "*.*", searchOption)) { // 先检查取消,避免执行后续操作 token.ThrowIfCancellationRequested(); await AddFileAsync(filePath); // FileCount是UI绑定属性,在UI线程更新更安全 await Application.Current.Dispatcher.InvokeAsync(() => FileCount++); } // 如果任务被取消,再次确保集合清空 if (token.IsCancellationRequested) { await Application.Current.Dispatcher.InvokeAsync(() => { Files.Clear(); FileCount = 0; }); } }
添加文件至集合函数
public ObservableCollection<FileModel> Files { get; set; } private async Task AddFileAsync(string filePath) { // 使用InvokeAsync返回Task,更符合异步语法 await Application.Current.Dispatcher.InvokeAsync(() => { var file = new FileModel { FileName = Path.GetFileName(filePath), FilePath = filePath }; Files.Add(file); }, DispatcherPriority.Background); }
额外注意点
ObservableCollection不是线程安全的,所有修改操作必须在UI线程执行,代码里已经通过Dispatcher保证了这一点。- 如果
SearchAsync不是事件处理方法,建议把返回类型从async void改成async Task,方便上层调用者处理异步逻辑。 - 取消令牌要贯穿所有可能的耗时操作,确保任务能及时响应取消请求。
内容的提问来源于stack exchange,提问作者SideSky
相关产品推荐
相关产品推荐

