ShopifySharp实现IRequestExecutionPolicy时async方法签名不匹配问题
ShopifySharp自定义请求执行策略实现问题解答
核心误解澄清
async修饰符不会改变方法的公开签名,IRequestExecutionPolicy接口要求Run方法返回Task<RequestResult<T>>,添加async修饰符后方法的返回类型依然符合要求,不会导致接口实现失败。
你看到的[AsyncStateMachine(typeof(SmartRetryExecutionPolicy.d__5<>))]标识是编译器编译async方法时自动生成的元数据,用于标记编译器生成的异步状态机类,属于正常现象,完全不影响代码正确性,和你遇到的“未实现错误”无关。
未实现错误的常见排查方向
- 检查所有用到的类型的命名空间是否正确:
CloneableRequestMessage、ExecuteRequestAsync、RequestResult都要使用ShopifySharp类库中定义的版本,不要自己创建同名类型导致签名不匹配。 - 检查泛型参数约束:如果接口的
Run方法的泛型参数T没有额外约束,你实现的时候也不能给T添加自定义约束。 - 可以使用IDE的自动实现接口功能生成方法骨架,再添加async修饰符和业务逻辑,避免手动写方法签名时出现拼写、参数顺序、可空性不匹配的问题。
正确实现示例
using ShopifySharp.Infrastructure; using System.Net.Http; using System.Threading; using System.Threading.Tasks; public class LeakyBucketExecutionPolicy : IRequestExecutionPolicy { // 可自定义漏桶配置参数,比如最大容量、每秒填充速率 private readonly int _maxBurst = 40; private readonly double _restoreRate = 2; private double _availableTokens; private DateTime _lastRefillTime = DateTime.UtcNow; private readonly object _lock = new object(); public LeakyBucketExecutionPolicy() { _availableTokens = _maxBurst; } public async Task<RequestResult<T>> Run<T>(CloneableRequestMessage requestMessage, ExecuteRequestAsync<T> executeRequestAsync, CancellationToken cancellationToken, int? graphqlQueryCost = null) { // 1. 漏桶令牌等待逻辑 await WaitForTokenAsync(graphqlQueryCost ?? 1, cancellationToken); while (true) { // 2. 克隆请求,避免重试时请求内容已被消费 using var clonedRequest = await requestMessage.CloneAsync(cancellationToken); try { var result = await executeRequestAsync(clonedRequest); // 3. 处理限流响应 if (result.Response.StatusCode == System.Net.HttpStatusCode.TooManyRequests) { var retryAfter = result.Response.Headers.RetryAfter?.Delta?.TotalSeconds ?? 1; await Task.Delay(TimeSpan.FromSeconds(retryAfter), cancellationToken); continue; } return result; } catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests) { await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); } } } private Task WaitForTokenAsync(int cost, CancellationToken cancellationToken) { lock (_lock) { // 先填充从上一次请求到现在新增的令牌 var now = DateTime.UtcNow; var timePassed = (now - _lastRefillTime).TotalSeconds; _availableTokens = Math.Min(_maxBurst, _availableTokens + timePassed * _restoreRate); _lastRefillTime = now; // 令牌足够就直接返回 if (_availableTokens >= cost) { _availableTokens -= cost; return Task.CompletedTask; } // 令牌不足计算等待时间 var neededTokens = cost - _availableTokens; var waitTime = neededTokens / _restoreRate; return Task.Delay(TimeSpan.FromSeconds(waitTime), cancellationToken); } } }
注意事项
- 请求重试时必须调用
CloneAsync()方法克隆原请求对象,HttpRequestMessage本身只能被发送一次,直接复用原对象会抛出请求内容已被消费的异常。 - 处理GraphQL请求时可以用传入的
graphqlQueryCost参数来扣除对应的令牌数量,适配GraphQL的成本计数规则。
内容的提问来源于stack exchange,提问作者Caleb Annunziello
相关产品推荐
相关产品推荐

