How to limit number of HttpWebRequest per second towards a webserver?

前端 未结 3 1456
日久生厌
日久生厌 2021-02-09 05:37

I need to implement a throttling mechanism (requests per second) when using HttpWebRequest for making parallel requests towards one application server. My C# app must issue no m

3条回答
  •  青春惊慌失措
    2021-02-09 06:19

    I had the same problem and couldn't find a ready solution so I made one, and here it is. The idea is to use a BlockingCollection to add items that need processing and use Reactive Extensions to subscribe with a rate-limited processor.

    Throttle class is the renamed version of this rate limiter

    public static class BlockingCollectionExtensions
    {
        // TODO: devise a way to avoid problems if collection gets too big (produced faster than consumed)
        public static IObservable AsRateLimitedObservable(this BlockingCollection sequence, int items, TimeSpan timePeriod, CancellationToken producerToken)
        {
            Subject subject = new Subject();
    
            // this is a dummyToken just so we can recreate the TokenSource
            // which we will pass the proxy class so it can cancel the task
            // on disposal
            CancellationToken dummyToken = new CancellationToken();
            CancellationTokenSource tokenSource = CancellationTokenSource.CreateLinkedTokenSource(producerToken, dummyToken);
    
            var consumingTask = new Task(() =>
            {
                using (var throttle = new Throttle(items, timePeriod))
                {
                    while (!sequence.IsCompleted)
                    {
                        try
                        {
                            T item = sequence.Take(producerToken);
                            throttle.WaitToProceed();
                            try
                            {
                                subject.OnNext(item);
                            }
                            catch (Exception ex)
                            {
                                subject.OnError(ex);
                            }
                        }
                        catch (OperationCanceledException)
                        {
                            break;
                        }
                    }
                    subject.OnCompleted();
                }
            }, TaskCreationOptions.LongRunning);
    
            return new TaskAwareObservable(subject, consumingTask, tokenSource);
        }
    
        private class TaskAwareObservable : IObservable, IDisposable
        {
            private readonly Task task;
            private readonly Subject subject;
            private readonly CancellationTokenSource taskCancellationTokenSource;
    
            public TaskAwareObservable(Subject subject, Task task, CancellationTokenSource tokenSource)
            {
                this.task = task;
                this.subject = subject;
                this.taskCancellationTokenSource = tokenSource;
            }
    
            public IDisposable Subscribe(IObserver observer)
            {
                var disposable = subject.Subscribe(observer);
                if (task.Status == TaskStatus.Created)
                    task.Start();
                return disposable;
            }
    
            public void Dispose()
            {
                // cancel consumption and wait task to finish
                taskCancellationTokenSource.Cancel();
                task.Wait();
    
                // dispose tokenSource and task
                taskCancellationTokenSource.Dispose();
                task.Dispose();
    
                // dispose subject
                subject.Dispose();
            }
        }
    }
    

    Unit test:

    class BlockCollectionExtensionsTest
    {
        [Fact]
        public void AsRateLimitedObservable()
        {
            const int maxItems = 1; // fix this to 1 to ease testing
            TimeSpan during = TimeSpan.FromSeconds(1);
    
            // populate collection
            int[] items = new[] { 1, 2, 3, 4 };
            BlockingCollection collection = new BlockingCollection();
            foreach (var i in items) collection.Add(i);
            collection.CompleteAdding();
    
            IObservable observable = collection.AsRateLimitedObservable(maxItems, during, CancellationToken.None);
            BlockingCollection processedItems = new BlockingCollection();
            ManualResetEvent completed = new ManualResetEvent(false);
            DateTime last = DateTime.UtcNow;
            observable
                // this is so we'll receive exceptions
                .ObserveOn(new SynchronizationContext()) 
                .Subscribe(item =>
                    {
                        if (item == 1)
                            last = DateTime.UtcNow;
                        else
                        {
                            TimeSpan diff = (DateTime.UtcNow - last);
                            last = DateTime.UtcNow;
    
                            Assert.InRange(diff.TotalMilliseconds,
                                during.TotalMilliseconds - 30,
                                during.TotalMilliseconds + 30);
                        }
                        processedItems.Add(item);
                    },
                    () => completed.Set()
                );
            completed.WaitOne();
            Assert.Equal(items, processedItems, new CollectionEqualityComparer());
        }
    }
    

提交回复
热议问题