异步方法重叠执行优化:确保耗时任务仅单次执行
解决异步方法重叠执行的问题
这个场景我太熟悉了——当异步方法被频繁调用,而它的执行时长超过调用间隔时,就会出现多个任务并行执行,重复跑那些耗时操作。咱们的目标很明确:让所有与首次调用重叠的后续调用,都等待同一个正在执行的任务完成,而不是各自启动新的任务。
核心思路
我们需要一个「共享任务」的机制:
- 用一个字段跟踪当前正在执行的异步任务
- 每次调用方法时,先检查是否有正在运行的任务:有就直接等待它;没有就创建新任务,并让后续调用都共享这个任务
- 用锁来处理并发场景下的任务赋值,避免竞态条件
实现代码
基于你给出的示例,修改后的代码如下:
public class AsyncTaskThrottler { // 锁对象:保证多线程下访问_currentTask的线程安全 private static readonly object _lockObj = new object(); // 跟踪当前正在执行的异步任务 private static Task _currentTask; public static void MethodA() { Console.Write("A"); } public static Task MethodB() { Console.Write("B"); return Task.Delay(500); } public static void MethodC() { Console.Write("C"); } public static async Task MethodAsync() { // 先检查是否有正在执行的任务,如果有直接等待 var existingTask = _currentTask; if (existingTask != null) { await existingTask; return; } // 没有正在执行的任务,加锁创建新任务 lock (_lockObj) { // 双重检查:防止在等待锁的过程中,其他线程已经创建了任务 existingTask = _currentTask; if (existingTask != null) { await existingTask; return; } // 创建并保存当前任务,注意这里不能直接await,要先赋值再执行 _currentTask = ExecuteCoreAsync(); await _currentTask; } } // 封装实际的业务逻辑 private static async Task ExecuteCoreAsync() { try { MethodA(); await MethodB(); MethodC(); } finally { // 任务完成后,重置_currentTask,让下一次调用可以创建新任务 lock (_lockObj) { _currentTask = null; } } } }
代码解释
- 锁与双重检查:
- 第一次检查
_currentTask是为了快速路径,避免每次都进入锁 - 进入锁后的第二次检查是关键:防止多个线程同时进入「创建任务」的分支,导致重复创建任务
- 第一次检查
- 任务跟踪与重置:
_currentTask保存当前正在执行的任务,所有重叠调用都会等待这个任务- 在
ExecuteCoreAsync的finally块中重置_currentTask,确保无论任务成功还是失败,后续调用都能正常启动新任务
- 业务逻辑封装:把实际要执行的
MethodA/MethodB/MethodC放到单独的ExecuteCoreAsync里,让核心逻辑和去重逻辑分离,更易维护
测试验证
如果你连续调用三次MethodAsync:
await Task.WhenAll( AsyncTaskThrottler.MethodAsync(), AsyncTaskThrottler.MethodAsync(), AsyncTaskThrottler.MethodAsync() );
预期输出是ABC(只执行一次业务逻辑),而不是AAABBBCCC(重叠执行),完全符合你的需求。
注意事项
- 如果
MethodB可能抛出异常,finally块会保证_currentTask被重置,不会影响后续调用 - 这个实现是静态类,适用于全局单例的场景;如果需要实例级别的去重,把
_lockObj和_currentTask改成实例字段即可
内容的提问来源于stack exchange,提问作者Arokh
相关产品推荐
相关产品推荐

