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

基于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);
}

额外注意点

  1. ObservableCollection 不是线程安全的,所有修改操作必须在UI线程执行,代码里已经通过 Dispatcher 保证了这一点。
  2. 如果 SearchAsync 不是事件处理方法,建议把返回类型从 async void 改成 async Task,方便上层调用者处理异步逻辑。
  3. 取消令牌要贯穿所有可能的耗时操作,确保任务能及时响应取消请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 05:36:08