简体   繁体   中英

How to limit method usage per amount of time?

It has to be trivial, but I just cannot get through it. I have to limit amount of tasks (let's say connections, emails sent or clicks in the button) per amount of time. So eg I can send 1000 emails per hour.

How can I do that in c#? I don't know and don't care how much time each operation will take. I just want to make sure that for last hour, only 1000 will be executed.

 class EventLimiter
 {
    Queue<DateTime> requestTimes;
    int maxRequests;
    TimeSpan timeSpan;

    public EventLimiter(int maxRequests, TimeSpan timeSpan)
    {
        this.maxRequests = maxRequests;
        this.timeSpan = timeSpan;
        requestTimes = new Queue<DateTime>(maxRequests);
    }

    private void SynchronizeQueue()
    {
        while ((requestTimes.Count > 0) && (requestTimes.Peek().Add(timeSpan) < DateTime.UtcNow))
            requestTimes.Dequeue();
    }

    public bool CanRequestNow()
    {
        SynchronizeQueue();
        return requestTimes.Count < maxRequests;
    }

    public void EnqueueRequest()
    {
        while (!CanRequestNow())               
            Thread.Sleep(requestTimes.Peek().Add(timeSpan).Subtract(DateTime.UtcNow));
            // Was: System.Threading.Thread.Sleep(1000);

        requestTimes.Enqueue(DateTime.UtcNow);
    }
 }

Assuming a rolling hour window:

Maintain a list of when actions were done.

Each time you want to do your action, remove all in the list not within the hour.

If there are fewer than 1000 then do the action and add a record to your list.


Assuming hourly:

Create a proxy method and a variable that is incremented for every action, and reduced to zero on the hour.

Do your action if the counter is < 1000.

The above solution looked fine. Here is my trimmed down version:

public class EmailRateHelper
{
    private int _requestsPerInterval;
    private Queue<DateTime> _history;
    private TimeSpan _interval;

    public EmailRateHelper()
        : this(30, new TimeSpan(0, 1, 0)) { }

    public EmailRateHelper(int requestsPerInterval, TimeSpan interval)
    {
        _requestsPerInterval = requestsPerInterval;
        _history = new Queue<DateTime>();
        _interval = interval;
    }

    public void SleepAsNeeded()
    {
        DateTime now = DateTime.Now;

        _history.Enqueue(now);

        if (_history.Count >= _requestsPerInterval)
        {
            var last = _history.Dequeue();                
            TimeSpan difference = now - last;

            if (difference < _interval)
            {
                System.Threading.Thread.Sleep(_interval - difference);
            }
        }
    }
}

您可以使用Rx扩展( 如何在Rx中使用新的BufferWithTimeOrCount返回IObservable <IObservable <T >>而不是IObservable <IList <T >> ),但是我将通过添加适当的代理对象来手动实现缓冲。

You may also consider storing {action, time, user} information in a database and get number of actions in a last hour fomr the DB (or similar persisted storager) if you need to handle Application pool restarts / crashes. Otherwise clever user may circumvent your in-memory protection with overloading your server.

You can create a persistent counter for every user. Every time you receive a request (for sending an email) you need to check the value of the counter and the date of the counter creation.

  • If the count is greater than the limit you refuse the request
  • If the date is older than an hour you reset the counter and set the new creation date
  • If the date is correct and the count is under the limit you increase the counter

Only in the last two cases the request is executed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM