В параллельном вызове ограничьте выполнение в секунду

Использование TPL/Parallel.ForEach — это готовый способ ограничить количество вызовов метода в единицу времени (т. е. не более 50 вызовов в секунду). Это отличается от ограничения количества потоков. Возможно, есть какой-то простой хак, чтобы заставить это работать?


comment
Не могли бы вы подробнее объяснить, что именно вам нужно и зачем?   -  person svick    schedule 03.05.2013
comment
Мне нужно параллельно вызывать веб-API, но API ограничивает количество вызовов в секунду. Я хочу оставаться в рамках этого лимита.   -  person SFun28    schedule 03.05.2013


Ответы (3)


Одним из решений является создание потокобезопасной версии следующего https://stackoverflow.com/a/7728872/356790.

/// <summary>
/// This class limits the number of requests (method calls, events fired, etc.) that can occur in a given unit of time.
/// </summary>
class RequestLimiter
{

    #region Constructors

    /// <summary>
    /// Initializes an instance of the RequestLimiter class.
    /// </summary>
    /// <param name="maxRequests">The maximum number of requests that can be made in a given unit of time.</param>
    /// <param name="timeSpan">The unit of time that the maximum number of requests is limited to.</param>
    /// <exception cref="ArgumentException">maxRequests &lt;= 0</exception>
    /// <exception cref="ArgumentException">timeSpan.TotalMilliseconds &lt;= 0</exception>
    public RequestLimiter( int maxRequests , TimeSpan timeSpan )
    {
        // check parameters
        if ( maxRequests <= 0 )
        {
            throw new ArgumentException( "maxRequests <= 0" , "maxRequests" );
        }
        if ( timeSpan.TotalMilliseconds <= 0 )
        {
            throw new ArgumentException( "timeSpan.TotalMilliseconds <= 0" , "timeSpan" );
        }

        // initialize instance vars
        _maxRequests = maxRequests;
        _timeSpan = timeSpan;
        _requestTimes = new Queue<DateTime>( maxRequests );

        // sleep for 1/10th timeSpan
        _sleepTimeInMs = Convert.ToInt32( Math.Ceiling( timeSpan.TotalMilliseconds / 10 ) );
    }

    #endregion

    /// <summary>
    /// Waits until an request can be made
    /// </summary>
    public void WaitUntilRequestCanBeMade()
    {
        while ( !TryEnqueueRequest() )
        {
            Thread.Sleep( _sleepTimeInMs );
        }
    }

    #region Private Members

    private readonly Queue<DateTime> _requestTimes;
    private readonly object _requestTimesLock = new object();
    private readonly int _maxRequests;
    private readonly TimeSpan _timeSpan;
    private readonly int _sleepTimeInMs;

    /// <summary>
    /// Remove requests that are older than _timeSpan
    /// </summary>
    private void SynchronizeQueue()
    {
        while ( ( _requestTimes.Count > 0 ) && ( _requestTimes.Peek().Add( _timeSpan ) < DateTime.Now ) )
        {
            _requestTimes.Dequeue();
        }
    }

    /// <summary>
    /// Attempts to enqueue a request.
    /// </summary>
    /// <returns>
    /// Returns true if the request was successfully enqueued.  False if not.
    /// </returns>
    private bool TryEnqueueRequest()
    {
        lock ( _requestTimesLock )
        {
            SynchronizeQueue();
            if ( _requestTimes.Count < _maxRequests )
            {
                _requestTimes.Enqueue( DateTime.Now );
                return true;
            }
            return false;
        }
    }

    #endregion

}
person SFun28    schedule 06.05.2013
comment
Вы, кажется, ответили на другой вопрос. Необходимость ограничения выполнения (для параллельного вызова веб-API) не подразумевает такие суперсложности, как выполнение большего количества выполнений/запросов, чем лимит, и помещение в очередь/задержка избытка в очередь! - person Gennady Vanin Геннадий Вани&; 06.05.2013
comment
Я понятия не имею, что ты пытаешься сказать. Я пытаюсь ограничить количество вызовов X/секунду. Это достигается вызовом функции WaitUntilRequestCanBeMade() перед выполнением вызова API. - person SFun28; 06.05.2013
comment
Чтобы ограничить количество выполнений (вызовов или чего-то еще), нет необходимости ставить их в очередь/удалять из очереди. Только не делайте больше звонков в секунду, чем необходимый лимит. Так просто, как, что! В своем ответе я дал ссылки на готовые примеры кода таких реализаций без очереди/де-очереди. Если поток звонков находится вне вашего контроля, то ваш ответ является правильным подходом, но не имеет ничего общего с вашим вопросом в том виде, в каком он был опубликован. Вы публикуете (недоопределенный) вопрос, отвечайте на другой и принимайте его! - person Gennady Vanin Геннадий Вани&; 06.05.2013
comment
Я думаю, что вы слишком узко сосредоточены на своем решении, чтобы увидеть достоинства моего. Только не делайте больше вызовов в секунду, чем требуемый лимит загруженной выписки. Вы просто сбрасываете звонки, которые превышают лимит? Если нет, то вы должны как-то поставить их в очередь. Что делать, если веб-служба имеет общесистемное ограничение, и вы вызываете более одного метода? Решение, которое я представил, хорошо работает во всех случаях и эффективно. Я не говорю, что ваше решение ошибочно, у меня просто нет причин исследовать его, потому что у меня есть решение. - person SFun28; 07.05.2013
comment
Между прочим, я вижу, что вы только что заминусовали кучу моих постов, и я уверен, что у вас нет опыта во всех этих областях, чтобы определить, какие из них являются правильными решениями =) Это не лучшее поведение, чтобы довести до это сообщество. Принятие ответов не увеличивает очки репутации. Я часто принимаю свои собственные ответы, когда думаю, что это поможет другим пользователям. Я специально не выбрал ваш ответ, потому что ни одна из ссылок напрямую не решает опубликованную проблему, тогда как мое решение можно скопировать и вставить. - person SFun28; 07.05.2013
comment
Мне это очень нравится, оно простое в использовании и означает, что я могу создавать столько разных объектов ограничения запросов в зависимости от моего использования. Я использую это, чтобы ограничить звонки на такие вещи, как facebook, twitter и другие социальные сети. - person jimplode; 01.10.2014

Примеры готового кода с использованием Timer:

  • для .NET 4.0 TPL Не пропустите это, это настоящий проект, всех времен и всех народов!
  • в C# 5.0/.NET 4.5, используя async/wait, WPF

Образцы/примеры кода с использованием Reactive Extensions (Rx):

person Gennady Vanin Геннадий Вани&    schedule 06.05.2013

Это решение обеспечивает задержку между запуском каждого потока и может использоваться для выполнения ваших требований.

    private SemaphoreSlim CooldownLock = new SemaphoreSlim(1, 1);
    private DateTime lastAction;

    private void WaitForCooldown(TimeSpan delay)
    {
        CooldownLock.Wait();

        var waitTime = delay - (DateTime.Now - lastAction);

        if (waitTime > TimeSpan.Zero)
        {
            Task.Delay(waitTime).Wait();
            lastAction = DateTime.Now;
        }

        lastAction = DateTime.Now;

        CooldownLock.Release();
    }

    public void Execute(Action[] actions, int concurrentThreadLimit, TimeSpan threadDelay)
    {
        if (actions.Any())
        {
            Parallel.ForEach(actions, 
                             new ParallelOptions() { MaxDegreeOfParallelism = concurrentThreadLimit}, 
                            (currentAction) =>
                            {
                                WaitForCooldown(threadDelay);
                                currentAction();
                            });
        }
    }
person mcwyrm    schedule 26.07.2017