2016-06-13 131 views
2

我有一個執行HTTP請求的函數。我通過一個簡單的程序來使用這個函數。我需要限制這些HTTP請求,以便不超過速率限制。在JavaScript世界中,有一個非常方便的third party library,它提供了一個返回一個新函數的函數,該函數調用您自己的函數,但會排隊調用,以便每分鐘只發生X次或其他任何事情。C#是否有內置的方式來調節函數的調用次數?

有沒有一個內置的C#方式做到這一點,或者一個方便的模式或nuget pkg爲此?

+0

您可以通過'Task.Delay(...)'或通過計算自上次調用之後的時間差(它大於您的閾值)手動輕鬆完成此操作。 – SharpShade

+0

如果通話頻率接近或超過約15ms的定時器分辨率,@SharpShade不起作用。另外,這不允許併發呼叫。 – usr

+0

我假設,雖然他實際上想要限制請求方法,但頻率超出了定時器分辨率。特別是因爲你可能至少有~5 + ms(取決於你的服務器)來實際處理請求。 – SharpShade

回答

4

沒有內置的方式。你需要找到一個庫或者自己實現它。

0

不,在.Net Framework中沒有內置的限制。您需要建立自己的或找到現有的庫。

我認爲最接近你可以得到withinn框架是設置限制號碼線程的電話,如Parallel.Foreach

1

正如其他答案所說,你將不得不自己實現這一點。幸運的是,這很容易。就個人而言,我會創建一個Queue<ObjectWithYourFunction'sArguments>。然後你可能會有一個ThrottledFunction排隊填滿,如果需要,啓動一個後臺任務,以等待適當的時間長度。

完全未經測試示例代碼:

class ThrottleMyFunction 
{ 
    private class Arguments 
    { 
     public int coolInt; 
     public double whatever; 
     public string stringyThing; 
    } 

    private ConcurrentQueue<Arguments> _argQueue = new Queue<Arguments>(); 
    private Task _loop; 

    //If you want to do "X times per minute", replace this argument with an int and use TimeSpan.FromMinutes(1/waitBetweenCalls) 
    public ThrottleMyFunction(TimeSpan waitBetweenCalls) 
    { 
     _loop = Task.Factory.StartNew(() => 
     { 
      Arguments args; 
      while (true) 
      { 
       if(_argQueue.TryDequeue(out args)) 
        FunctionIWantToThrottle(args.coolInt, args.whatever, args.stringyThing); 
       } 

       Thread.Sleep(waitBetweenCalls); 

      } 
     }); 
    } 

    public void ThrottledFunction(int coolerInt, double whatevs, string stringy) 
    { 
     _argQueue.Enqueue(new Arguments() { coolInt = coolerInt, whatever = whatevs, stringyThing = stringy }); 
    } 
} 
+1

我會使用'ConcurrentQueue'。這是一個Web應用程序,所以你可以將項目排入多個線程,並可能在另一個線程上出隊。 –

+0

Good suggetsion,@ScottHannen。無論如何,我還是有點想這麼做。 –

1

我寫了這一點,但就其本質測試這將是一個有點複雜。 (IOW沒有測試。)

推測是你有一個Action<TParameters>來調用,以便動作作爲參數傳遞給構造函數。然後執行,你可以撥打Enqueue(TParameters parameters)

它將使您的項目入隊並開始處理隊列(除非隊列已被處理)。每次執行都會增加一個計數器,並且當計數器達到每個時間間隔的最大執行次數時,執行就會停止。

還有一個指定時間間隔的計時器。當該計時器過去時,執行次數被重置並且隊列被處理(除非它已經被處理)。這樣,已經等待間隔結束的項目被處理。

這是對您的要求的字面解釋。它不會在一個時間間隔內均勻地傳播呼叫,而且時間在兩者之間。這意味着如果您只能在一秒鐘內打一次給定的電話三次,它會立即發出前三個電話,然後等到第二個電話過去後再撥打另外3個電話。其目標是無需等待即可使用您擁有的任何容量,然後等待更多容量可用並無需等待即可使用。

using System; 
using System.Collections.Concurrent; 
using System.Threading; 
using Timer = System.Timers.Timer; 

namespace Throttler 
{ 
    public abstract class ExecutionThrottler<TParameters> : IDisposable 
    { 
     private readonly Action<TParameters> _action; 
     private readonly int _executionsPerInterval; 
     private readonly ConcurrentQueue<TParameters> _queue = new ConcurrentQueue<TParameters>(); 
     private bool _processingQueue; 
     private readonly object _processingQueueLock = new object(); 
     private int _executionsSinceIntervalStart; 
     private readonly Timer _timer; 
     bool _disposed; 

     protected ExecutionThrottler(Action<TParameters> action, TimeSpan interval, int executionsPerInterval) 
     { 
      _action = action; 
      _executionsPerInterval = executionsPerInterval; 
      _timer = new Timer(interval.TotalMilliseconds); 
      _timer.AutoReset = true; 
      _timer.Start(); 
      _timer.Elapsed += OnIntervalEnd; 
     } 

     public void Enqueue(TParameters parameters) 
     { 
      _queue.Enqueue(parameters); 
     } 

     private void TryProcessQueue() 
     { 
      if (_processingQueue) return; 
      lock (_processingQueueLock) 
      { 
       if (_processingQueue) return; 
       _processingQueue = true; 
       try 
       { 
        ProcessQueue(); 
       } 
       finally 
       { 
        _processingQueue = false; 
       } 
      } 
     } 

     private void ProcessQueue() 
     { 
      TParameters dequeuedParameters; 
      while ((_executionsSinceIntervalStart < _executionsPerInterval) && _queue.TryDequeue(out dequeuedParameters)) 
      { 
       Interlocked.Increment(ref _executionsSinceIntervalStart); 
       _action.Invoke(dequeuedParameters); 
      } 
     } 


     private void OnIntervalEnd(object sender, System.Timers.ElapsedEventArgs e) 
     { 
      _executionsSinceIntervalStart = 0; 
      TryProcessQueue(); 
     } 

     public void Dispose() 
     { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
     } 

     ~ExecutionThrottler() 
     { 
      Dispose(false); 
     } 

     protected virtual void Dispose(bool disposing) 
     { 
      if (_disposed) 
       return; 

      if (disposing) 
      { 
       _timer.Dispose(); 
      } 
      _disposed = true; 
     } 
    } 
} 

更新:編輯刪除不必要的使用Interlocked,以確保原子讀/寫。唯一需要的操作是遞增執行操作的次數。

+0

更新到此:我過度使用了「聯鎖」。 '_executionsSinceIntervalStart'可以是一個'int',除了遞增之外,所有其他的操作都是原子的。 –

相關問題