C# 包装速率限制API调用

C# 包装速率限制API调用,c#,.net,asynchronous,concurrency,system.reactive,C#,.net,Asynchronous,Concurrency,System.reactive,我访问了一个API调用,每秒接受最大调用速率。如果超过速率,将抛出一个异常 我想将这个调用包装成一个抽象,它做了必要的工作,以将调用速率保持在限制之下。它的作用就像一个网络路由器:处理多个呼叫,并将结果返回给正确的呼叫方,关心呼叫速率。目标是使调用代码尽可能不知道该限制。否则,代码中具有此调用的每个部分都必须包装到一个try-catch中 例如:想象一下,您可以从一个可以添加2个数字的外部API调用一个方法。此API每秒可调用5次。任何高于此值的值都将导致异常 为了说明问题,限制通话速率的外部服

我访问了一个API调用,每秒接受最大调用速率。如果超过速率,将抛出一个异常

我想将这个调用包装成一个抽象,它做了必要的工作,以将调用速率保持在限制之下。它的作用就像一个网络路由器:处理多个呼叫,并将结果返回给正确的呼叫方,关心呼叫速率。目标是使调用代码尽可能不知道该限制。否则,代码中具有此调用的每个部分都必须包装到一个try-catch中

例如:想象一下,您可以从一个可以添加2个数字的外部API调用一个方法。此API每秒可调用5次。任何高于此值的值都将导致异常

为了说明问题,限制通话速率的外部服务与此问题答案中的服务类似

其他信息:

由于您不希望在每次从代码的任何部分调用此方法时都担心该限制,因此可以考虑设计一个包装器方法,您可以在不担心速率限制的情况下调用该方法。在内部,您关心限制,但在外部,您公开了一个简单的异步方法

它类似于web服务器。它如何将正确的结果包返回给正确的客户

多个调用者将调用此方法,并在调用时获得结果。这个抽象应该像代理一样工作

我怎么做呢

我相信包装方法的公司应该是这样的

public async Task<Results> MyMethod()
公共异步任务MyMethod()
在方法内部,它将执行逻辑,可能使用反应式扩展(缓冲区)。我不知道

但是怎么做呢?我的意思是,对这个方法的多次调用应该会将结果返回给正确的调用方。这可能吗


多谢各位

实现这一点的一种变体是确保通话间隔最短,如下所示:

private readonly Object syncLock = new Object();
private readonly TimeSpan minTimeout = TimeSpan.FromSeconds(5);
private volatile DateTime nextCallDate = DateTime.MinValue;

public async Task<Result> RequestData(...) {
    DateTime possibleCallDate = DateTime.Now;
    lock(syncLock) {
        // When is it possible to make the next call?
        if (nextCallDate > possibleCallDate) {
            possibleCallDate = nextCallDate;
        }
        nextCallDate = possibleCallDate + minTimeout;
    }

    TimeSpan waitingTime = possibleCallDate - DateTime.Now;
    if (waitingTime > TimeSpan.Zero) {
        await Task.Delay(waitingTime);
    }

    return await ... /* the actual call to API */ ...;
}
private readonly Object syncLock=new Object();
private readonly TimeSpan minTimeout=TimeSpan.FromSeconds(5);
private volatile DateTime nextCallDate=DateTime.MinValue;
公共异步任务RequestData(…){
DateTime possibleCallDate=DateTime.Now;
锁定(同步锁定){
//什么时候可以打下一个电话?
如果(下一个调用日期>可能调用日期){
possibleCallDate=nextCallDate;
}
nextCallDate=possibleCallDate+minTimeout;
}
TimeSpan waitingTime=可能的CallDate-DateTime.Now;
如果(waitingTime>TimeSpan.Zero){
等待任务。延迟(waitingTime);
}
return wait…/*实际调用API*/。。。;
}

实现此功能的一种变体是确保通话间隔最短,如下所示:

private readonly Object syncLock = new Object();
private readonly TimeSpan minTimeout = TimeSpan.FromSeconds(5);
private volatile DateTime nextCallDate = DateTime.MinValue;

public async Task<Result> RequestData(...) {
    DateTime possibleCallDate = DateTime.Now;
    lock(syncLock) {
        // When is it possible to make the next call?
        if (nextCallDate > possibleCallDate) {
            possibleCallDate = nextCallDate;
        }
        nextCallDate = possibleCallDate + minTimeout;
    }

    TimeSpan waitingTime = possibleCallDate - DateTime.Now;
    if (waitingTime > TimeSpan.Zero) {
        await Task.Delay(waitingTime);
    }

    return await ... /* the actual call to API */ ...;
}
private readonly Object syncLock=new Object();
private readonly TimeSpan minTimeout=TimeSpan.FromSeconds(5);
private volatile DateTime nextCallDate=DateTime.MinValue;
公共异步任务RequestData(…){
DateTime possibleCallDate=DateTime.Now;
锁定(同步锁定){
//什么时候可以打下一个电话?
如果(下一个调用日期>可能调用日期){
possibleCallDate=nextCallDate;
}
nextCallDate=possibleCallDate+minTimeout;
}
TimeSpan waitingTime=可能的CallDate-DateTime.Now;
如果(waitingTime>TimeSpan.Zero){
等待任务。延迟(waitingTime);
}
return wait…/*实际调用API*/。。。;
}

你到底应该做什么取决于你的目标和限制。我的假设:

  • 您希望避免在速率限制生效时发出请求
  • 您无法预测某个特定请求是否会被拒绝,或者需要多长时间才能再次允许另一个请求
  • 您不需要同时发出多个请求,当多个请求正在等待时,它们以何种顺序完成并不重要
如果这些假设是有效的,您可以使用:在发出请求之前等待设置,在成功发出请求之后设置,以及在速率受限的延迟之后设置

代码可以如下所示:

class RateLimitedWrapper<TException> where TException : Exception
{
    private readonly AsyncAutoResetEvent autoResetEvent = new AsyncAutoResetEvent(set: true);

    public async Task<T> Execute<T>(Func<Task<T>> func) 
    {
        while (true)
        {
            try
            {
                await autoResetEvent.WaitAsync();

                var result = await func();

                autoResetEvent.Set();

                return result;
            }
            catch (TException)
            {
                var ignored = Task.Delay(500).ContinueWith(_ => autoResetEvent.Set());
            }
        }
    }
}
class RateLimitedWrapper,其中TexException:异常
{
private readonly AsyncAutoResetEvent autoResetEvent=new AsyncAutoResetEvent(设置为true);
公共异步任务执行(Func Func)
{
while(true)
{
尝试
{
等待autoResetEvent.WaitAsync();
var result=await func();
autoResetEvent.Set();
返回结果;
}
捕获(特克斯例外)
{
忽略var=Task.Delay(500).ContinueWith(=>autoResetEvent.Set());
}
}
}
}
用法:

public static Task<int> Add(int a, int b)
{
    return rateLimitedWrapper.Execute(() => rateLimitingCalculator.Add(a, b));
}
公共静态任务添加(inta,intb)
{
return rateLimitedWrapper.Execute(()=>rateLimitingCalculator.Add(a,b));
}

你到底应该做什么取决于你的目标和限制。我的假设:

  • 您希望避免在速率限制生效时发出请求
  • 您无法预测某个特定请求是否会被拒绝,或者需要多长时间才能再次允许另一个请求
  • 您不需要同时发出多个请求,当多个请求正在等待时,它们以何种顺序完成并不重要
如果这些假设是有效的,您可以使用:在发出请求之前等待设置,在成功发出请求之后设置,以及在速率受限的延迟之后设置

代码可以如下所示:

class RateLimitedWrapper<TException> where TException : Exception
{
    private readonly AsyncAutoResetEvent autoResetEvent = new AsyncAutoResetEvent(set: true);

    public async Task<T> Execute<T>(Func<Task<T>> func) 
    {
        while (true)
        {
            try
            {
                await autoResetEvent.WaitAsync();

                var result = await func();

                autoResetEvent.Set();

                return result;
            }
            catch (TException)
            {
                var ignored = Task.Delay(500).ContinueWith(_ => autoResetEvent.Set());
            }
        }
    }
}
class RateLimitedWrapper,其中TexException:异常
{
private readonly AsyncAutoResetEvent autoResetEvent=new AsyncAutoResetEvent(设置为true);
公共异步任务执行(Func Func)
{