繁体   English   中英

使用Parallel.ForEach限制每秒启动的API请求数

[英]Limiting number of API requests started per second using Parallel.ForEach

我正在努力改进我的一些代码以提高效率。 在原始代码中,我限制了允许为5的线程数,如果我已经有5个活动线程,我会等到一个完成后再启动另一个。 现在我想修改此代码以允许任意数量的线程,但我希望能够确保每秒只启动5个线程。 例如:

  • 第二个0 - 5个新主题
  • 第二个1 - 5个新主题
  • 第二个2-5个新线程......

原始代码(cleanseDictionary通常包含数千项):

        ConcurrentDictionary<long, APIResponse> cleanseDictionary = new ConcurrentDictionary<long, APIResponse>();
        ConcurrentBag<int> itemsinsec = new ConcurrentBag<int>();
        ConcurrentDictionary<long, string> resourceDictionary = new ConcurrentDictionary<long, string>();
        DateTime start = DateTime.Now;

        Parallel.ForEach(resourceDictionary, new ParallelOptions { MaxDegreeOfParallelism = 5 }, row =>
        {
            lock (itemsinsec)
            {
                ThrottleAPIRequests(itemsinsec, start);

                itemsinsec.Add(1);
            }

            cleanseDictionary.TryAdd(row.Key, _helper.MakeAPIRequest(string.Format("/endpoint?{0}", row.Value)));
        });


    private static void ThrottleAPIRequests(ConcurrentBag<int> itemsinsec, DateTime start)
    {
        if ((start - DateTime.Now).Milliseconds < 10001 && itemsinsec.Count > 4)
        {
            System.Threading.Thread.Sleep(1000 - (start - DateTime.Now).Milliseconds);
            start = DateTime.Now;
            itemsinsec = new ConcurrentBag<int>();
        }
    }

我的第一个想法是将MaxDegreeofParallelism增加到更高的值,然后有一个辅助方法,一秒钟内只限制5个线程,但我不确定这是否是最好的方法,如果是,我可能需要lock那一步?

提前致谢!

编辑我实际上正在寻找一种方法来限制API请求而不是实际的线程。 我以为他们是同一个人。

编辑2:我的要求是每秒发送5个以上的API请求

来自MS网站的“Parallel.ForEach”

可以并行运行

如果您想要对线程的管理方式进行任何程度的精细控制,那就不是这样了。
如何创建自己的帮助程序类,您可以使用组ID对作业进行排队,允许您等待组ID X的所有作业完成,并在需要时生成额外的线程?

我希望每秒能够发送超过5个API请求

这很简单:

while (true) {
 await Task.Delay(TimeSpan.FromSeconds(1));
 await Task.WhenAll(Enumerable.Range(0, 5).Select(_ => RunRequestAsync()));
}

也许不是最好的方法,因为会有一连串的请求。 这不是连续的。

此外,还有时间偏差。 一次迭代需要1秒以上。 这可以通过几行时间逻辑来解决。

对我来说,最好的解决方案是:

using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace SomeNamespace
{
    public class RequestLimiter : IRequestLimiter
    {
        private readonly ConcurrentQueue<DateTime> _requestTimes;
        private readonly TimeSpan _timeSpan;

        private readonly object _locker = new object();

        public RequestLimiter()
        {
            _timeSpan = TimeSpan.FromSeconds(1);
            _requestTimes = new ConcurrentQueue<DateTime>();
        }

        public TResult Run<TResult>(int requestsOnSecond, Func<TResult> function)
        {
            WaitUntilRequestCanBeMade(requestsOnSecond).Wait();
            return function();
        }

        private Task WaitUntilRequestCanBeMade(int requestsOnSecond)
        {
            return Task.Factory.StartNew(() =>
            {
                while (!TryEnqueueRequest(requestsOnSecond).Result) ;
            });
        }

        private Task SynchronizeQueue()
        {
            return Task.Factory.StartNew(() =>
            {
                _requestTimes.TryPeek(out var first);

                while (_requestTimes.Count > 0 && (first.Add(_timeSpan) < DateTime.UtcNow))
                    _requestTimes.TryDequeue(out _);
            });
        }

        private Task<bool> TryEnqueueRequest(int requestsOnSecond)
        {
            lock (_locker)
            {
                SynchronizeQueue().Wait();
                if (_requestTimes.Count < requestsOnSecond)
                {
                    _requestTimes.Enqueue(DateTime.UtcNow);
                    return Task.FromResult(true);
                }
                return Task.FromResult(false);
            }
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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