【问题标题】:In parallel call, limit executions per second在并行调用中,限制每秒执行次数
【发布时间】:2017-07-27 00:15:29
【问题描述】:

使用 TPL / Parallel.ForEach 有一种开箱即用的方法来限制每单位时间调用方法的次数(即每秒不超过 50 次调用)。这与限制线程数不同。也许有一些简单的 hack 可以使这项工作?

【问题讨论】:

  • 您能否更详细地解释一下您究竟需要什么以及为什么?
  • 我需要并行调用一个 Web API,但是这个 API 限制了每秒的调用次数。我想保持在这个限制之下。

标签: .net-4.0 task-parallel-library .net-4.5 parallel-extensions parallel.foreach


【解决方案1】:

一种解决方案是制作以下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

}

【讨论】:

  • 您似乎回答了另一个问题。 “限制执行” ("to call a web API in parallel") 的必要性并不意味着这种 super-over-complications 比限制更多的执行/请求和排队/延迟其超出限制排队!
  • 我不知道你想说什么。我正在尝试将调用次数限制为 X/秒。这是通过在调用 API 之前调用 WaitUntilRequestCanBeMade() 来完成的。
  • 为了限制执行次数(调用或其他),不需要对它们进行排队/出列。只是不要每秒拨打超过所需限制的电话。就如此容易! In my answer 我提供了对此类实现的现成代码示例的引用,而无需任何排队/出队。如果呼叫流程超出您的控制范围,那么您的答案是一种有效的方法,但与您发布的问题无关。您发布(未指定)问题,回答另一个问题并接受它!
  • 顺便说一句,我看到你刚刚对我的一些帖子投了反对票,我很确定你在所有这些领域都没有专业知识来确定哪些是有效的解决方案 =)这不是带给这个社区的最佳行为。接受答案不会增加声誉点数。当我认为它会帮助其他用户时,我经常接受我自己的答案。我没有特别选择你的答案,因为没有一个链接直接解决发布的问题,而我的解决方案可以复制粘贴。
  • 我真的很喜欢这个,使用简单,意味着我可以根据我的使用创建尽可能多的不同请求限制对象。我正在使用它来限制对 Facebook、Twitter 和其他社交媒体等内容的调用。
【解决方案2】:

使用 Timer 的现成代码示例:

使用响应式扩展 (Rx) 的代码示例/示例:

【讨论】:

    【解决方案3】:

    此解决方案在每个线程的启动之间强制执行延迟,可用于满足您的要求。

        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();
                                });
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-05-19
      • 2018-08-26
      • 2012-02-14
      • 1970-01-01
      • 1970-01-01
      • 2016-07-12
      • 1970-01-01
      相关资源
      最近更新 更多