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

如何在后台长任务循环中等待条件/事件触发以继续迭代?

用信号量实现事件触发式预缓存(替代轮询方案)

核心思路

要避免后台任务的轮询浪费,关键是让后台任务在缓存满时进入等待状态,只有当主循环处理完缓存项(空出位置)时,才主动通知后台任务继续填充。这里用SemaphoreSlim就能实现这种异步的等待/通知机制,完美适配你的场景。

修改后的代码示例

public class SomeClass
{
    private readonly ConcurrentQueue<int> _sharedData = [];
    private Task? _backgroundTask;
    // 信号量初始计数设为缓存阈值5,最多允许同时填充5个缓存项
    private readonly SemaphoreSlim _cacheSemaphore = new(5);
    private const int CacheThreshold = 5;

    private async Task Start()
    {
        using CancellationTokenSource cts = new();
        _backgroundTask = Task.Run(() => BackgroundWork(cts.Token), cts.Token);

        for (int i = 0; i < 100; i++)
        {
            if (_sharedData.TryDequeue(out int data))
            {
                // 取出缓存项后,释放一个信号量名额,通知后台可以加新的
                _cacheSemaphore.Release();
                Status = string.Join(", ", _sharedData.Select(a => a.ToString()));
            }
            await Task.Delay(300);
        }
        cts.Cancel();
        try
        {
            await _backgroundTask;
        }
        catch (OperationCanceledException)
        {
            // 取消异常是预期情况,不用处理
        }
    }

    private async Task BackgroundWork(CancellationToken ct)
    {
        try
        {
            while (!ct.IsCancellationRequested)
            {
                // 异步等待信号量,直到有空位或者任务被取消
                await _cacheSemaphore.WaitAsync(ct);
                
                // 检查是否已取消,避免取消后还执行添加操作
                if (ct.IsCancellationRequested) break;
                
                _sharedData.Enqueue(Random.Shared.Next());
                Status = string.Join(", ", _sharedData.Select(a => a.ToString()));
            }
        }
        catch (OperationCanceledException)
        {
            // 捕获取消异常,正常退出
        }
        finally
        {
            Status = "bg task done.";
            _cacheSemaphore.Dispose();
        }
    }
}

代码说明

  1. 信号量初始化:初始给5个名额,对应你的缓存阈值,后台任务可以先填满5个缓存项,之后每取出一个,就释放一个名额,后台才能再加一个。
  2. 主循环操作:每次成功取出队列里的元素后,调用Release()释放信号量,相当于告诉后台“有空位了,可以加新的”。
  3. 后台任务逻辑:不再循环检查队列数量,而是等着信号量的通知,只有当有空闲名额或者任务被取消时,才会继续执行添加操作,完全避免了无效轮询。
  4. 取消处理:增加了对取消令牌的检查,确保任务能及时响应取消请求,不会做无用功。

适配实际文件缓存场景的调整

如果是实际的文件预加载场景,你可以这么改:

  • 把ConcurrentQueue<int>换成存储文件信息或缓存内容的队列,比如ConcurrentQueue<FileCacheItem>。
  • 把Enqueue(Random.Shared.Next())替换成异步的文件下载逻辑,比如await DownloadFileFromServerAsync(nextFileUrl, ct),这样不会阻塞线程。
  • 根据文件大小、网络速度调整缓存阈值,比如设置成3-5个文件,平衡预加载速度和内存占用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 19:41:00