C#是否有内置的方法来限制函数的调用次数?

时间:2016-06-13 16:18:17

标签: c#

我有一个执行HTTP请求的函数。我在我编写的一个简单程序中使用了这个函数。我需要限制这些HTTP请求,以便我不超过速率限制。在JavaScript世界中,有一个非常方便的third party library,它提供了一个throttle函数,它返回一个调用自己函数的新函数,但会将调用排队,这样它们每分钟只发生X次或者其他

是否有内置的C#方式,或者这是一个方便的模式或nuget pkg?

6 个答案:

答案 0 :(得分:5)

没有内置方式。您需要找到一个库或自己实现它。

答案 1 :(得分:5)

如果您使用Reactive Extensions,则可以使用Observable.Throttle()方法:https://msdn.microsoft.com/en-us/library/hh229400(v=vs.103).aspx

可以在http://reactivex.io/

找到“Reactive Extensions”页面

答案 2 :(得分:3)

正如其他答案所说,你必须自己实施。幸运的是,这很容易。就个人而言,我会创建一个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 });
    }
}

答案 3 :(得分:1)

我写过这篇文章,但就其性质而言,测试会有点复杂。 (现在没有经过测试。)

假设你有一个Action<TParameters>来调用,所以这个动作作为参数传递给构造函数。然后执行,你打电话给Enqueue(TParameters parameters)

它将使您的项目入队并开始处理队列(除非队列已被处理。)每次执行都会增加一个计数器,当计数器达到每个间隔的最大执行次数时,执行将停止。

还有指定间隔的计时器。当该计时器过去时,重置执行次数并处理队列(除非它已被处理。)这样就等待了等待间隔结束的项目。

这是对您的要求的字面解释。它不是在一个间隔内均匀地传播呼叫,它们之间有时间。这意味着如果你只能在一秒钟内进行3次特定呼叫,它将立即进行前3次呼叫,然后等到第二次呼叫再进行3次呼叫。目标是在不等待的情况下使用您拥有的任何容量,然后等待更多容量变为可用并使用它而无需等待。

using 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使用以确保原子读/写。唯一需要它的操作是递增执行操作的计数。

答案 4 :(得分:0)

不,.Net Framework中没有内置限制功能。您需要构建自己的库或找到现有的库。

我认为您在Framework中最接近的是为Parallel.Foreach之类的调用设置数字线程限制。

答案 5 :(得分:0)

这可以通过创建一个计时器和一个句柄来完成这个诀窍来实现,这里有一个如何链接http://sstut.com/csharpdotnet/javascript-timers-equivalent.php