Использование TPL/Parallel.ForEach — это готовый способ ограничить количество вызовов метода в единицу времени (т. е. не более 50 вызовов в секунду). Это отличается от ограничения количества потоков. Возможно, есть какой-то простой хак, чтобы заставить это работать?
В параллельном вызове ограничьте выполнение в секунду
comment
Не могли бы вы подробнее объяснить, что именно вам нужно и зачем?
- person svick   schedule 03.05.2013
comment
Мне нужно параллельно вызывать веб-API, но API ограничивает количество вызовов в секунду. Я хочу оставаться в рамках этого лимита.
- person SFun28   schedule 03.05.2013
Ответы (3)
Одним из решений является создание потокобезопасной версии следующего https://stackoverflow.com/a/7728872/356790.
/// <summary>
/// This class limits the number of requests (method calls, events fired, etc.) that can occur in a given unit of time.
/// </summary>
class RequestLimiter
{
#region Constructors
/// <summary>
/// Initializes an instance of the RequestLimiter class.
/// </summary>
/// <param name="maxRequests">The maximum number of requests that can be made in a given unit of time.</param>
/// <param name="timeSpan">The unit of time that the maximum number of requests is limited to.</param>
/// <exception cref="ArgumentException">maxRequests <= 0</exception>
/// <exception cref="ArgumentException">timeSpan.TotalMilliseconds <= 0</exception>
public RequestLimiter( int maxRequests , TimeSpan timeSpan )
{
// check parameters
if ( maxRequests <= 0 )
{
throw new ArgumentException( "maxRequests <= 0" , "maxRequests" );
}
if ( timeSpan.TotalMilliseconds <= 0 )
{
throw new ArgumentException( "timeSpan.TotalMilliseconds <= 0" , "timeSpan" );
}
// initialize instance vars
_maxRequests = maxRequests;
_timeSpan = timeSpan;
_requestTimes = new Queue<DateTime>( maxRequests );
// sleep for 1/10th timeSpan
_sleepTimeInMs = Convert.ToInt32( Math.Ceiling( timeSpan.TotalMilliseconds / 10 ) );
}
#endregion
/// <summary>
/// Waits until an request can be made
/// </summary>
public void WaitUntilRequestCanBeMade()
{
while ( !TryEnqueueRequest() )
{
Thread.Sleep( _sleepTimeInMs );
}
}
#region Private Members
private readonly Queue<DateTime> _requestTimes;
private readonly object _requestTimesLock = new object();
private readonly int _maxRequests;
private readonly TimeSpan _timeSpan;
private readonly int _sleepTimeInMs;
/// <summary>
/// Remove requests that are older than _timeSpan
/// </summary>
private void SynchronizeQueue()
{
while ( ( _requestTimes.Count > 0 ) && ( _requestTimes.Peek().Add( _timeSpan ) < DateTime.Now ) )
{
_requestTimes.Dequeue();
}
}
/// <summary>
/// Attempts to enqueue a request.
/// </summary>
/// <returns>
/// Returns true if the request was successfully enqueued. False if not.
/// </returns>
private bool TryEnqueueRequest()
{
lock ( _requestTimesLock )
{
SynchronizeQueue();
if ( _requestTimes.Count < _maxRequests )
{
_requestTimes.Enqueue( DateTime.Now );
return true;
}
return false;
}
}
#endregion
}
person
SFun28
schedule
06.05.2013
Вы, кажется, ответили на другой вопрос. Необходимость ограничения выполнения (для параллельного вызова веб-API) не подразумевает такие суперсложности, как выполнение большего количества выполнений/запросов, чем лимит, и помещение в очередь/задержка избытка в очередь!
- person Gennady Vanin Геннадий Вани&; 06.05.2013
Я понятия не имею, что ты пытаешься сказать. Я пытаюсь ограничить количество вызовов X/секунду. Это достигается вызовом функции WaitUntilRequestCanBeMade() перед выполнением вызова API.
- person SFun28; 06.05.2013
Чтобы ограничить количество выполнений (вызовов или чего-то еще), нет необходимости ставить их в очередь/удалять из очереди. Только не делайте больше звонков в секунду, чем необходимый лимит. Так просто, как, что! В своем ответе я дал ссылки на готовые примеры кода таких реализаций без очереди/де-очереди. Если поток звонков находится вне вашего контроля, то ваш ответ является правильным подходом, но не имеет ничего общего с вашим вопросом в том виде, в каком он был опубликован. Вы публикуете (недоопределенный) вопрос, отвечайте на другой и принимайте его!
- person Gennady Vanin Геннадий Вани&; 06.05.2013
Я думаю, что вы слишком узко сосредоточены на своем решении, чтобы увидеть достоинства моего. Только не делайте больше вызовов в секунду, чем требуемый лимит загруженной выписки. Вы просто сбрасываете звонки, которые превышают лимит? Если нет, то вы должны как-то поставить их в очередь. Что делать, если веб-служба имеет общесистемное ограничение, и вы вызываете более одного метода? Решение, которое я представил, хорошо работает во всех случаях и эффективно. Я не говорю, что ваше решение ошибочно, у меня просто нет причин исследовать его, потому что у меня есть решение.
- person SFun28; 07.05.2013
Между прочим, я вижу, что вы только что заминусовали кучу моих постов, и я уверен, что у вас нет опыта во всех этих областях, чтобы определить, какие из них являются правильными решениями =) Это не лучшее поведение, чтобы довести до это сообщество. Принятие ответов не увеличивает очки репутации. Я часто принимаю свои собственные ответы, когда думаю, что это поможет другим пользователям. Я специально не выбрал ваш ответ, потому что ни одна из ссылок напрямую не решает опубликованную проблему, тогда как мое решение можно скопировать и вставить.
- person SFun28; 07.05.2013
Мне это очень нравится, оно простое в использовании и означает, что я могу создавать столько разных объектов ограничения запросов в зависимости от моего использования. Я использую это, чтобы ограничить звонки на такие вещи, как facebook, twitter и другие социальные сети.
- person jimplode; 01.10.2014
Примеры готового кода с использованием Timer:
- для .NET 4.0 TPL Не пропустите это, это настоящий проект, всех времен и всех народов!
- в C# 5.0/.NET 4.5, используя
async/wait
, WPF
Образцы/примеры кода с использованием Reactive Extensions (Rx):
- Метод Observable.Interval (TimeSpan, IScheduler), пример MSDN
- пример Observable.Timer(), RxWiki
person
Gennady Vanin Геннадий Вани&
schedule
06.05.2013
Это решение обеспечивает задержку между запуском каждого потока и может использоваться для выполнения ваших требований.
private SemaphoreSlim CooldownLock = new SemaphoreSlim(1, 1);
private DateTime lastAction;
private void WaitForCooldown(TimeSpan delay)
{
CooldownLock.Wait();
var waitTime = delay - (DateTime.Now - lastAction);
if (waitTime > TimeSpan.Zero)
{
Task.Delay(waitTime).Wait();
lastAction = DateTime.Now;
}
lastAction = DateTime.Now;
CooldownLock.Release();
}
public void Execute(Action[] actions, int concurrentThreadLimit, TimeSpan threadDelay)
{
if (actions.Any())
{
Parallel.ForEach(actions,
new ParallelOptions() { MaxDegreeOfParallelism = concurrentThreadLimit},
(currentAction) =>
{
WaitForCooldown(threadDelay);
currentAction();
});
}
}
person
mcwyrm
schedule
26.07.2017