如何用CancellationTokenSource实现邮件延迟标记已读(中途切换取消)?
实现邮件延迟标记已读的正确方案
核心逻辑要求
- 打开未读邮件后,延迟3秒将其标记为已读
- 若在超时前点击其他邮件,当前邮件保持未读(即取消当前的延迟标记任务)
正确实现代码
private readonly object _ctsLock = new object(); private CancellationTokenSource? _currentCts; private async Task OnEmailClicked(int id) { // 取消并清理之前未完成的延迟任务 lock (_ctsLock) { _currentCts?.Cancel(); _currentCts?.Dispose(); _currentCts = null; } // 加载目标邮件内容 await LoadEmail(id); // 创建新的取消令牌源,绑定当前任务 CancellationTokenSource currentTaskCts; lock (_ctsLock) { currentTaskCts = _currentCts = new CancellationTokenSource(); } try { // 等待3秒,期间若收到取消信号则抛出TaskCanceledException await Task.Delay(3000, currentTaskCts.Token); } catch (TaskCanceledException) { // 任务被取消,直接返回,不执行标记已读操作 return; } finally { // 仅清理当前任务对应的令牌源,避免误清理新任务的资源 lock (_ctsLock) { if (_currentCts == currentTaskCts) { _currentCts.Dispose(); _currentCts = null; } } } // 延迟完成且未被取消,执行标记已读操作 await MarkEmailAsRead(id); }
关键实现细节
- 线程安全保护:使用
lock对象保护对_currentCts的所有操作,避免多线程并发点击时出现竞态条件(比如多个线程同时修改或清理令牌源) - 令牌源生命周期管理:
- 每次点击新邮件时,先取消并清理之前的令牌源,确保旧任务不会继续执行
- 创建新令牌源时,在锁内完成赋值,保证后续取消操作能精准指向当前任务
finally块中仅清理当前任务对应的令牌源,防止新邮件点击创建的令牌源被误清理
- 取消逻辑处理:捕获
TaskCanceledException后直接返回,终止当前任务的后续流程,确保被中断的邮件不会被标记为已读
内容的提问来源于stack exchange,提问作者lonix
相关产品推荐
相关产品推荐

