使用GraphServiceClient调用MS Graph API手动缓冲请求出现重复ID问题
问题根因
- 核心原因是C#的循环变量闭包捕获陷阱:for循环中声明的迭代变量
i在整个循环周期内是同一个引用实例,你传入Task.Run的匿名异步函数捕获的是变量i本身,而非每次循环时i的瞬时值。你之前的串行实现是每次循环都await任务完成才会进入下一次迭代,i的取值和任务执行时机完全同步,所以不会出现问题;但修改为并行后,提交完的任务不会立即执行,等任务真正开始执行读取usersAADList[i]时,循环已经完成了多次i的自增,所有未执行的任务拿到的都是当前i的最新值,就会出现批量重复请求同一个用户、输出重复ID的现象。 - 额外问题:
FillOneUserAuthMethods本身就是返回Task的异步方法,不需要额外套一层Task.Run,属于无意义的线程池调度开销。
修复代码
直接在循环体内创建局部变量存储当前迭代的用户对象,让lambda捕获该局部变量(每次循环迭代的局部变量都是独立实例,不会被后续循环修改),同时移除冗余的Task.Run即可:
private static async Task FillUsersAuthMethods(GraphServiceClient graphClient, List<POCO.userAAD> usersAADList) { List<Task<string>> bufferTasksList = new List<Task<string>>(); const int maxParallel = 4; for (int i = 0; i < usersAADList.Count; ++i) { // 关键:循环内部定义局部变量,存储当前迭代的用户对象,lambda捕获该独立局部变量 var currentUser = usersAADList[i]; bufferTasksList.Add(FillOneUserAuthMethods(graphClient, currentUser)); if (bufferTasksList.Count >= maxParallel || i + 1 == usersAADList.Count) { await Task.WhenAll(bufferTasksList); bufferTasksList.Clear(); } } }
可选优化方案
如果需要更灵活的并发度控制,也可以用SemaphoreSlim实现,代码更简洁不易出错:
private static async Task FillUsersAuthMethods(GraphServiceClient graphClient, List<POCO.userAAD> usersAADList) { using var semaphore = new SemaphoreSlim(4); // 最多4个并发 var tasks = usersAADList.Select(async user => { await semaphore.WaitAsync(); try { await FillOneUserAuthMethods(graphClient, user); } finally { semaphore.Release(); } }); await Task.WhenAll(tasks); }
内容的提问来源于stack exchange,提问作者KUL
相关产品推荐
相关产品推荐

