我有一個執行HTTP請求的函數。我通過一個簡單的程序來使用這個函數。我需要限制這些HTTP請求,以便不超過速率限制。在JavaScript世界中,有一個非常方便的third party library,它提供了一個返回一個新函數的函數,該函數調用您自己的函數,但會排隊調用,以便每分鐘只發生X次或其他任何事情。C#是否有內置的方式來調節函數的調用次數?
有沒有一個內置的C#方式做到這一點,或者一個方便的模式或nuget pkg爲此?
我有一個執行HTTP請求的函數。我通過一個簡單的程序來使用這個函數。我需要限制這些HTTP請求,以便不超過速率限制。在JavaScript世界中,有一個非常方便的third party library,它提供了一個返回一個新函數的函數,該函數調用您自己的函數,但會排隊調用,以便每分鐘只發生X次或其他任何事情。C#是否有內置的方式來調節函數的調用次數?
有沒有一個內置的C#方式做到這一點,或者一個方便的模式或nuget pkg爲此?
沒有內置的方式。你需要找到一個庫或者自己實現它。
不,在.Net Framework中沒有內置的限制。您需要建立自己的或找到現有的庫。
我認爲最接近你可以得到withinn框架是設置限制號碼線程的電話,如Parallel.Foreach
。
如果使用無擴展,你可以利用Observable.Throttle()
方法:https://msdn.microsoft.com/en-us/library/hh229400(v=vs.103).aspx
無功擴展頁可以在http://reactivex.io/
這可以用很少的代碼創建一個定時器來完成和被發現處理伎倆,這裏是一個如何鏈接http://sstut.com/csharpdotnet/javascript-timers-equivalent.php
正如其他答案所說,你將不得不自己實現這一點。幸運的是,這很容易。就個人而言,我會創建一個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 });
}
}
我會使用'ConcurrentQueue'。這是一個Web應用程序,所以你可以將項目排入多個線程,並可能在另一個線程上出隊。 –
Good suggetsion,@ScottHannen。無論如何,我還是有點想這麼做。 –
我寫了這一點,但就其本質測試這將是一個有點複雜。 (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
,以確保原子讀/寫。唯一需要的操作是遞增執行操作的次數。
更新到此:我過度使用了「聯鎖」。 '_executionsSinceIntervalStart'可以是一個'int',除了遞增之外,所有其他的操作都是原子的。 –
您可以通過'Task.Delay(...)'或通過計算自上次調用之後的時間差(它大於您的閾值)手動輕鬆完成此操作。 – SharpShade
如果通話頻率接近或超過約15ms的定時器分辨率,@SharpShade不起作用。另外,這不允許併發呼叫。 – usr
我假設,雖然他實際上想要限制請求方法,但頻率超出了定時器分辨率。特別是因爲你可能至少有~5 + ms(取決於你的服務器)來實際處理請求。 – SharpShade