使用Parallel.ForEach

时间:2016-10-14 14:04:01

标签: c# multithreading 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请求

3 个答案:

答案 0 :(得分:1)

&#34; Parallel.ForEach&#34;来自MS网站

  

可以并行运行

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

答案 1 :(得分:0)

  

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

这真的很容易:

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

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

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

答案 2 :(得分:0)

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

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