基于Microsoft Graph的Planner任务数量检索性能优化及重试策略需求
我之前在做类似的Planner对接项目时也碰到过批量Plan任务计数慢的问题,结合你的Angular+ASP.NET架构,给你分享几个实用的优化方案和重试机制实现思路:
一、性能优化:从39秒到秒级响应的关键调整
1. 用Graph批量请求($batch)减少HTTP开销
逐个请求205个Plan的任务数量,光是HTTP握手和连接建立就占了大部分时间。Microsoft Graph支持批量请求,可以把最多20个独立请求打包成一个POST请求发送,大幅减少网络往返次数。
举个ASP.NET里的实现示例:
var batchRequestContent = new BatchRequestContent(); var planIds = // 你的205个Plan ID集合 int batchSize = 20; // .NET 6+可用Chunk,低版本可自行实现分块逻辑 var batches = planIds.Chunk(batchSize); foreach (var batch in batches) { foreach (var planId in batch) { var countRequest = _graphServiceClient.Planner.Plans[planId].Tasks.Count.Request(); var requestId = Guid.NewGuid().ToString(); batchRequestContent.AddBatchRequestStep(countRequest, requestId); } var batchResponse = await _graphServiceClient.Batch.Request().PostAsync(batchRequestContent); // 解析每个请求的计数结果 foreach (var requestId in batchRequestContent.BatchRequestSteps.Keys) { var countResponse = await batchResponse.GetResponseAsync<int>(requestId); // 处理countResponse.Body的数值,比如存入数据库或返回前端 } }
这样205个Plan只需要11个批量请求,能把时间压缩到几秒内。
2. 直接请求任务计数,不要拉取全量任务
别傻乎乎地把所有任务拉回来再Count()!Graph支持直接返回任务数量,用$count参数:
var taskCount = await _graphServiceClient.Planner.Plans[planId].Tasks .Count .Request() .GetAsync();
如果需要过滤特定状态的任务(比如只算未完成任务),可以加$filter:
var activeTaskCount = await _graphServiceClient.Planner.Plans[planId].Tasks .Count .Request() .Filter("percentComplete ne 100") .GetAsync();
这个方法能让Graph服务器帮你做计数,不用传输大量任务数据,带宽和处理时间都省了。
3. 加入缓存策略
如果你的任务数量不需要实时到秒级,可以给计数结果加缓存。ASP.NET里用IMemoryCache或者分布式缓存(比如Redis):
public async Task<int> GetPlanTaskCountAsync(string planId) { var cacheKey = $"Planner_TaskCount_{planId}"; if (_memoryCache.TryGetValue(cacheKey, out int count)) { return count; } count = await _graphServiceClient.Planner.Plans[planId].Tasks.Count.Request().GetAsync(); // 设置5分钟过期,可根据业务调整 _memoryCache.Set(cacheKey, count, TimeSpan.FromMinutes(5)); return count; }
重复请求同一个Plan时直接读缓存,完全跳过Graph调用。
4. 并行请求+限流控制
如果不想用批量请求,也可以用Task.WhenAll并行发起请求,但一定要控制并发数,避免触发Graph的速率限制(429错误):
var semaphore = new SemaphoreSlim(10); // 限制同时10个请求 var tasks = planIds.Select(async planId => { await semaphore.WaitAsync(); try { return await _graphServiceClient.Planner.Plans[planId].Tasks.Count.Request().GetAsync(); } finally { semaphore.Release(); } }); var counts = await Task.WhenAll(tasks);
这种方式比串行快,但还是不如批量请求高效。
二、实现可靠的重试机制
对接Graph一定要处理429(速率限制)、5xx(服务器错误)这类临时错误,推荐用Polly库来实现重试策略,比手动写重试逻辑灵活得多。
1. 安装Polly并配置重试策略
首先安装Polly包:
# NuGet包管理器安装 Install-Package Polly # 或.NET CLI安装 dotnet add package Polly
然后在Program.cs里注册重试策略(ASP.NET 5写法):
var retryPolicy = Policy .Handle<HttpRequestException>() .OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.TooManyRequests || (int)r.StatusCode >= 500) .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) // 指数退避:2s→4s→8s );
2. 在Graph调用中使用重试策略
把Graph请求包在Polly的策略里:
var taskCount = await retryPolicy.ExecuteAsync(async () => { return await _graphServiceClient.Planner.Plans[planId].Tasks.Count.Request().GetAsync(); });
如果遇到429或5xx错误,会自动重试3次,每次间隔翻倍,避免频繁请求加重服务器负担。
3. 处理Graph的Retry-After头
当Graph返回429时,会附带Retry-After头告诉你多久后可以重试。Polly可以配置读取这个头来动态调整等待时间:
var retryPolicy = Policy .Handle<HttpRequestException>() .OrResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.TooManyRequests) .WaitAndRetryAsync(3, (retryAttempt, response, context) => { if (response.Result.StatusCode == HttpStatusCode.TooManyRequests && response.Result.Headers.TryGetValues("Retry-After", out var values) && int.TryParse(values.First(), out var retryAfter)) { return TimeSpan.FromSeconds(retryAfter); } return TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)); });
这样更贴合Graph的限流规则,重试成功率更高。
最后提醒
别忘了注意Microsoft Graph Planner的速率限制,避免短时间内请求过多被限流,影响整体性能。
内容的提问来源于stack exchange,提问作者Leonardo Heis

