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

基于yield的批量文件搜索并发任务启动异常问题排查

文件搜索器并发处理问题排查与解决

问题1:并发任务无法按SemaphoreSlim设置并行启动

你实现了批量yield返回文件的搜索器,期望通过SemaphoreSlim控制8个并发任务,但实际必须等待前一个任务完成才会启动下一个,即便使用Task.Run也无法解决。

核心原因

  1. 同步枚举阻塞主线程:EnumerateFilesRecursively是同步IEnumerable,foreach迭代时,每次获取下一批文件会同步阻塞当前线程——必须等当前批次的文件扫描完成,才会进入下一次循环启动新任务,无法同时进行文件扫描和任务处理。
  2. 任务启动方式导致同步执行阻塞:原代码直接调用StartTasksAsync,该方法内部的foreach (var item in items)是同步执行的,这部分代码会在当前线程运行到第一个await(Task.Delay)前阻塞线程,导致下一次循环无法及时启动。

解决方法

步骤1:将文件枚举改为异步IAsyncEnumerable

把同步文件扫描改成异步,避免阻塞主线程,让文件扫描和任务处理并行进行:

internal static async IAsyncEnumerable<IEnumerable<string>> EnumerateFilesRecursively(
    string directory, 
    int batchSize,
    [EnumeratorCancellation] CancellationToken cancellationToken)
{
    Stack<string> stack = new Stack<string>();
    List<string> currentFoundFiles = new List<string>();
    stack.Push(directory);

    while (stack.Count > 0)
    {
        cancellationToken.ThrowIfCancellationRequested();
        string currentDir = stack.Pop();

        try
        {
            // 异步获取子目录,避免同步阻塞
            foreach (string subDir in await Task.Run(() => Directory.GetDirectories(currentDir), cancellationToken))
                stack.Push(subDir);
        }
        catch (UnauthorizedAccessException)
        {
            Log.Error("File Searcher: UnauthorizedAccessException");
        }
        catch (DirectoryNotFoundException)
        {
            Log.Error("File Searcher: DirectoryNotFoundException");
        }

        try
        {
            // 异步获取文件,避免同步阻塞
            var files = await Task.Run(() => 
                Directory.GetFiles(currentDir, "*")
                    .Where(file => allowedExtensions.Contains(Path.GetExtension(file)))
                    .ToList(), cancellationToken);

            currentFoundFiles.AddRange(files);
        }
        catch (UnauthorizedAccessException)
        {
            Log.Error("File Searcher 2: UnauthorizedAccessException");
            continue;
        }
        catch (DirectoryNotFoundException)
        {
            Log.Error("File Searcher 2: DirectoryNotFoundException");
            continue;
        }

        if (currentFoundFiles.Count < batchSize) continue;
        // 返回副本,避免后续修改影响已返回的批次
        yield return currentFoundFiles.ToList();
        currentFoundFiles.Clear();
    }

    if(currentFoundFiles.Count > 0)
        yield return currentFoundFiles.ToList();
}

步骤2:修改RunAsync方法,异步枚举并正确启动任务

使用await foreach遍历异步枚举器,同时用Task.Run包装任务逻辑,确保任务完全在后台线程执行,不阻塞枚举过程:

public async Task RunAsync(CancellationToken cancellationToken)
{
    var tasks = new List<Task>();
    var index = 0;
    using var semaphore = new SemaphoreSlim(8, 16);
    // 异步枚举文件批次
    await foreach (var items in FileSearcher.EnumerateFilesRecursively(@"E:/nestedAAAA", batchSize, cancellationToken))
    {
        await semaphore.WaitAsync(cancellationToken);
        try
        {
            var localIndex = index;
            var localItems = items; // 捕获当前批次的副本
            // 用Task.Run包装任务,确保任务在后台线程执行
            var task = Task.Run(async () =>
            {
                try
                {
                    await StartTasksAsync(localItems, localIndex, cancellationToken);
                }
                finally
                {
                    semaphore.Release();
                }
            }, cancellationToken);
            tasks.Add(task);
            index++;
        }
        catch(Exception ex)
        {
            _logger.LogError(ex, "Error in HarvestEngine RunAsync");
            semaphore.Release();
        }
    }

    await Task.WhenAll(tasks);
}

问题2:Task.Run中仅最后一个任务打印所有项

简化代码后,发现只有最后一个任务能打印批次的所有文件,之前的任务仅打印第一个项。

核心原因

EnumerateFilesRecursively中,所有yield返回的IEnumerable<string>都是同一个currentFoundFiles列表的引用。当后续迭代调用currentFoundFiles.Clear()并添加新文件时,之前任务中引用的列表已经被修改,导致遍历到的内容不是当初yield的批次数据。

解决方法

在yield返回批次时,返回列表的副本,而不是原列表的引用:

// 原代码
// yield return currentFoundFiles;
// 修改为:
yield return currentFoundFiles.ToList();

这样每个批次都是独立的列表,后续修改原列表不会影响已返回的批次数据。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 06:08:12