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

如何在带循环的PeriodicTimer中捕获异常并持续执行?

解决方案:分离异常收集与定时器执行逻辑

核心思路

别在定时器的遍历循环里直接抛出AggregateException——只要未处理的异常从定时器执行逻辑里跑出来,PeriodicTimer就会直接终止后续周期。正确的做法是:

  • 遍历过程中把所有异常攒起来
  • 把攒好的异常传递给调用方,同时保证定时器循环能继续跑
  • 调用方负责把异常写入日志文件

具体实现代码

1. 定时器类实现(收集异常并通知)

用Action<AggregateException>类型的回调传递异常,比事件更简洁,不用额外定义事件参数:

public class ItemProcessor
{
    private readonly PeriodicTimer _timer;
    private readonly IEnumerable<object> _items; // 换成你的实际项类型
    private readonly Action<AggregateException>? _onExceptionsOccurred;
    private bool _isRunning;

    public ItemProcessor(TimeSpan period, IEnumerable<object> items, Action<AggregateException>? onExceptionsOccurred = null)
    {
        _timer = new PeriodicTimer(period);
        _items = items;
        _onExceptionsOccurred = onExceptionsOccurred;
    }

    public async Task StartProcessingAsync(CancellationToken cancellationToken = default)
    {
        if (_isRunning) return;
        _isRunning = true;

        try
        {
            while (await _timer.WaitForNextTickAsync(cancellationToken))
            {
                var exceptions = new List<Exception>();

                foreach (var item in _items)
                {
                    try
                    {
                        // 替换成你的实际处理逻辑
                        ProcessItem(item);
                    }
                    catch (Exception ex)
                    {
                        exceptions.Add(ex);
                    }
                }

                // 有异常时通知调用方,而非直接抛出
                if (exceptions.Count > 0)
                {
                    _onExceptionsOccurred?.Invoke(new AggregateException(exceptions));
                }
            }
        }
        finally
        {
            _isRunning = false;
        }
    }

    private void ProcessItem(object item)
    {
        // 你的业务逻辑,可能抛出异常
        if (item is int i && i % 3 == 0)
        {
            throw new InvalidOperationException($"Item {i} 处理失败");
        }
    }
}

2. 调用方实现(接收异常并写入日志)

调用方只需要传入处理异常的回调,负责把AggregateException写入文件:

public class LogWriter
{
    public async Task WriteExceptionsToFileAsync(AggregateException ex)
    {
        var logContent = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 批量异常:\n";
        foreach (var innerEx in ex.InnerExceptions)
        {
            logContent += $" - {innerEx.Message}\n{innerEx.StackTrace}\n\n";
        }

        await File.AppendAllTextAsync("error.log", logContent);
    }
}

// 使用示例
var items = Enumerable.Range(1, 10);
var logWriter = new LogWriter();
var processor = new ItemProcessor(TimeSpan.FromSeconds(5), items, async ex => await logWriter.WriteExceptionsToFileAsync(ex));

// 启动定时器,可通过CancellationToken停止
await processor.StartProcessingAsync();

为什么这个方案可行?

  • 定时器不会终止:遍历过程中捕获所有异常,不会让异常跑到PeriodicTimer的等待逻辑里,下一个周期能正常执行
  • 异常批量传递:用AggregateException打包所有异常,符合你“循环结束后汇总所有异常”的需求(用回调传递而非抛出,避免终止定时器)
  • 实现简洁:没有复杂的事件订阅逻辑,一个Action回调就搞定异常传递,代码更紧凑

替代方案:延迟抛出(如果调用方需要主动获取)

如果调用方不想被动接收,而是要主动获取异常,可以在定时器类里维护一个异常队列,让调用方定时读取:

public class ItemProcessor
{
    private readonly ConcurrentQueue<AggregateException> _exceptionQueue = new();
    // ... 其他字段

    public bool TryDequeueException(out AggregateException? ex)
    {
        return _exceptionQueue.TryDequeue(out ex);
    }

    // 在遍历结束后:
    if (exceptions.Count > 0)
    {
        _exceptionQueue.Enqueue(new AggregateException(exceptions));
    }
}

// 调用方定时读取:
while (true)
{
    if (processor.TryDequeueException(out var ex))
    {
        await logWriter.WriteExceptionsToFileAsync(ex);
    }
    await Task.Delay(TimeSpan.FromSeconds(1));
}

内容的提问来源于stack exchange,提问作者Álvaro García

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 14:30:24