如何在带循环的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
相关产品推荐
相关产品推荐

