我有一个执行HTTP请求的单一函数。 我在编写的简单程序中频繁地使用这个函数。
然而,我需要限制这些HTTP请求,以避免超过速率限制。 在JavaScript世界中,有一个非常方便的第三方库,它提供了一个throttle
函数,返回一个新函数调用您自己的函数,但会排队处理调用,使它们每分钟或每小时只发生X次。
那么,在C#中有没有内置的方法来实现这一点呢? 如果没有,我该如何完成这个功能呢?
Observable.Throttle()
方法:https://msdn.microsoft.com/en-us/library/hh229400(v=vs.103).aspx
响应式扩展的页面可以在http://reactivex.io/找到。没有内置的方法。您需要找到一个库或自己实现。
正如其他答案所说,您需要自己实现这个功能。幸运的是,这很容易。个人建议创建一个Queue<ObjectWithYourFunction'sArguments>
。然后,您可以创建一个ThrottledFunction
将其排队,如果需要,启动一个后台任务等待适当的时间。
以下是未经测试的示例代码:
class ThrottleMyFunction
{
private class Arguments
{
public int coolInt;
public double whatever;
public string stringyThing;
}
private ConcurrentQueue<Arguments> _argQueue = new ConcurrentQueue<Arguments>();
private Task _loop;
//If you want to do "X times per minute", replace this argument with an int and use TimeSpan.FromMinutes(1/waitBetweenCalls)
public void ThrottleMyFunction(TimeSpan waitBetweenCalls)
{
_loop = Task.Factory.StartNew(() =>
{
Arguments args;
while (true)
{
if (_argQueue.TryDequeue(out args))
FunctionIWantToThrottle(args.coolInt, args.whatever, args.stringyThing);
}
Thread.Sleep(waitBetweenCalls);
});
}
public void ThrottledFunction(int coolerInt, double whatevs, string stringy)
{
_argQueue.Enqueue(new Arguments() { coolInt = coolerInt, whatever = whatevs, stringyThing = stringy });
}
}
ConcurrentQueue
。这是一个Web应用程序,因此您将在多个线程上排队项目,并可能在另一个线程上出列。 - Scott Hannenusing System;
using System.Collections.Concurrent;
using System.Threading;
using Timer = System.Timers.Timer;
namespace Throttler
{
public abstract class ExecutionThrottler<TParameters> : IDisposable
{
private readonly Action<TParameters> _action;
private readonly int _executionsPerInterval;
private readonly ConcurrentQueue<TParameters> _queue = new ConcurrentQueue<TParameters>();
private bool _processingQueue;
private readonly object _processingQueueLock = new object();
private int _executionsSinceIntervalStart;
private readonly Timer _timer;
bool _disposed;
protected ExecutionThrottler(Action<TParameters> action, TimeSpan interval, int executionsPerInterval)
{
_action = action;
_executionsPerInterval = executionsPerInterval;
_timer = new Timer(interval.TotalMilliseconds);
_timer.AutoReset = true;
_timer.Start();
_timer.Elapsed += OnIntervalEnd;
}
public void Enqueue(TParameters parameters)
{
_queue.Enqueue(parameters);
}
private void TryProcessQueue()
{
if (_processingQueue) return;
lock (_processingQueueLock)
{
if (_processingQueue) return;
_processingQueue = true;
try
{
ProcessQueue();
}
finally
{
_processingQueue = false;
}
}
}
private void ProcessQueue()
{
TParameters dequeuedParameters;
while ((_executionsSinceIntervalStart < _executionsPerInterval) && _queue.TryDequeue(out dequeuedParameters))
{
Interlocked.Increment(ref _executionsSinceIntervalStart);
_action.Invoke(dequeuedParameters);
}
}
private void OnIntervalEnd(object sender, System.Timers.ElapsedEventArgs e)
{
_executionsSinceIntervalStart = 0;
TryProcessQueue();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~ExecutionThrottler()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_timer.Dispose();
}
_disposed = true;
}
}
}
Interlocked
以确保原子读/写。唯一需要它的操作是增加执行操作的计数。Interlocked
。 _executionsSinceIntervalStart
可以是一个 int
,除了增加之外的所有其他操作都已经是原子操作了。 - Scott Hannen这可以通过创建一个计时器和一个处理程序来实现,只需很少的代码就可以完成这个技巧。这里是一个如何链接的方法http://sstut.com/csharpdotnet/javascript-timers-equivalent.php
不,.Net Framework 中没有内置的限流功能。您需要构建自己的限流功能或查找现有的库。
我认为在框架内最接近的方法是对像 Parallel.Foreach
这样的调用设置线程数限制。
Task.Delay(...)
或手动计算自上次调用以来的时间差,如果大于你的阈值,则执行操作。 - SharpShade