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

前端 未结 3 1452
日久生厌
日久生厌 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:16

    My original post discussed how to add a throttling mechanism to WCF via client behavior extensions, but then was pointed out that I misread the question (doh!).

    Overall the approach can be to check with a class that determines if we are violating the rate limit or not. There's already been a lot of discussion around how to check for rate violations.

    Throttling method calls to M requests in N seconds

    If you are violating the rate limit, then sleep for a fix interval and check again. If not, then go ahead and make the HttpWebRequest call.

    0 讨论(0)
  • 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<T> 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<T> AsRateLimitedObservable<T>(this BlockingCollection<T> sequence, int items, TimeSpan timePeriod, CancellationToken producerToken)
        {
            Subject<T> subject = new Subject<T>();
    
            // 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<T>(subject, consumingTask, tokenSource);
        }
    
        private class TaskAwareObservable<T> : IObservable<T>, IDisposable
        {
            private readonly Task task;
            private readonly Subject<T> subject;
            private readonly CancellationTokenSource taskCancellationTokenSource;
    
            public TaskAwareObservable(Subject<T> subject, Task task, CancellationTokenSource tokenSource)
            {
                this.task = task;
                this.subject = subject;
                this.taskCancellationTokenSource = tokenSource;
            }
    
            public IDisposable Subscribe(IObserver<T> 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<int> collection = new BlockingCollection<int>();
            foreach (var i in items) collection.Add(i);
            collection.CompleteAdding();
    
            IObservable<int> observable = collection.AsRateLimitedObservable(maxItems, during, CancellationToken.None);
            BlockingCollection<int> processedItems = new BlockingCollection<int>();
            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<int>());
        }
    }
    
    0 讨论(0)
  • 2021-02-09 06:33

    The Throttle() and Sample() extension methods (On Observable) allow you to regulate a fast sequence of events into a "slower" sequence.

    Here is a blog post with an example of Sample(Timespan) that ensures a maxium rate.

    0 讨论(0)
提交回复
热议问题