You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 11:15:05