我遇到了一个问题,我需要限制对另一个Web服务器的调用次数。它会有所不同,因为服务器是共享的,也许它可能有更多或更少的容量。
我正在考虑使用SemaphoreSlim类,但是没有公共属性可以更改最大计数。
我应该将SemaphoreSlim类包装在另一个可以处理最大计数的类中吗?有没有更好的方法?
编辑:
以下是我正在尝试的内容:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Semaphore
{
class Program
{
static SemaphoreSlim _sem = new SemaphoreSlim(10,10000);
static void Main(string[] args)
{
int max = 15;
for (int i = 1; i <= 50; i++)
{
new Thread(Enter).Start(new int[] { i, max});
}
Console.ReadLine();
max = 11;
for (int i = 1; i <= 50; i++)
{
new Thread(Enter).Start(new int[] { i, max });
}
}
static void Enter(object param)
{
int[] arr = (int[])param;
int id = arr[0];
int max = arr[1];
try
{
Console.WriteLine(_sem.CurrentCount);
if (_sem.CurrentCount <= max)
_sem.Release(1);
else
{
_sem.Wait(1000);
Console.WriteLine(id + " wants to enter");
Thread.Sleep((1000 * id) / 2); // can be here at
Console.WriteLine(id + " is in!"); // Only three threads
}
}
catch(Exception ex)
{
Console.WriteLine("opps ", id);
Console.WriteLine(ex.Message);
}
finally
{
_sem.Release();
}
}
}
}
问题:
1-_sem.Wait(1000)应该取消执行超过1000ms的线程,不是吗?
2 - 我是否有使用发布/等待的想法?
答案 0 :(得分:9)
您无法更改最大数量,但您可以创建一个具有非常高的最大数量的SemaphoreSlim
,并保留其中一些。请参阅this constructor。
所以,让我们说绝对最大并发调用数是100,但最初你希望它是25.你初始化你的信号量:
SemaphoreSlim sem = new SemaphoreSlim(25, 100);
25是可以同时服务的请求数。你保留了另外75个。
如果您想增加允许的数量,请拨打Release(num)。如果您拨打了Release(10)
,则该号码将转到35。
现在,如果您想减少可用请求的数量,则必须多次调用WaitOne
。例如,如果要从可用计数中删除10:
for (var i = 0; i < 10; ++i)
{
sem.WaitOne();
}
这有可能阻塞,直到其他客户端释放信号量。也就是说,如果您允许35个并发请求,并且您希望将其减少到25个,但是已经有35个客户端具有活动请求,那么WaitOne
将阻塞,直到客户端调用Release
,并且循环赢了& #39; t终止,直到10个客户端发布。
答案 1 :(得分:2)
此时,您可以随心所欲地等待信号量(没有相应的释放呼叫)来降低容量。您可以多次释放信号量(没有相应的等待呼叫)以增加有效容量。
如果这是您做得足够多的事情,您可以创建自己的信号量类,组成SemaphoreSlim
并封装此逻辑。如果您的代码已经释放信号量而没有先等待它,那么这个组合也是必不可少的;使用您自己的类,您可以确保此类版本是无操作。 (也就是说,你应该避免把自己置于那个位置,真的。)
答案 2 :(得分:1)
好的,我可以在单声道项目上解决我的问题。
// SemaphoreSlim.cs
//
// Copyright (c) 2008 Jérémie "Garuma" Laval
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace System.Threading
{
public class SemaphoreSlimCustom : IDisposable
{
const int spinCount = 10;
const int deepSleepTime = 20;
private object _sync = new object();
int maxCount;
int currCount;
bool isDisposed;
public int MaxCount
{
get { lock (_sync) { return maxCount; } }
set
{
lock (_sync)
{
maxCount = value;
}
}
}
EventWaitHandle handle;
public SemaphoreSlimCustom (int initialCount) : this (initialCount, int.MaxValue)
{
}
public SemaphoreSlimCustom (int initialCount, int maxCount)
{
if (initialCount < 0 || initialCount > maxCount || maxCount < 0)
throw new ArgumentOutOfRangeException ("The initialCount argument is negative, initialCount is greater than maxCount, or maxCount is not positive.");
this.maxCount = maxCount;
this.currCount = initialCount;
this.handle = new ManualResetEvent (initialCount > 0);
}
public void Dispose ()
{
Dispose(true);
}
protected virtual void Dispose (bool disposing)
{
isDisposed = true;
}
void CheckState ()
{
if (isDisposed)
throw new ObjectDisposedException ("The SemaphoreSlim has been disposed.");
}
public int CurrentCount {
get {
return currCount;
}
}
public int Release ()
{
return Release(1);
}
public int Release (int releaseCount)
{
CheckState ();
if (releaseCount < 1)
throw new ArgumentOutOfRangeException ("releaseCount", "releaseCount is less than 1");
// As we have to take care of the max limit we resort to CAS
int oldValue, newValue;
do {
oldValue = currCount;
newValue = (currCount + releaseCount);
newValue = newValue > maxCount ? maxCount : newValue;
} while (Interlocked.CompareExchange (ref currCount, newValue, oldValue) != oldValue);
handle.Set ();
return oldValue;
}
public void Wait ()
{
Wait (CancellationToken.None);
}
public bool Wait (TimeSpan timeout)
{
return Wait ((int)timeout.TotalMilliseconds, CancellationToken.None);
}
public bool Wait (int millisecondsTimeout)
{
return Wait (millisecondsTimeout, CancellationToken.None);
}
public void Wait (CancellationToken cancellationToken)
{
Wait (-1, cancellationToken);
}
public bool Wait (TimeSpan timeout, CancellationToken cancellationToken)
{
CheckState();
return Wait ((int)timeout.TotalMilliseconds, cancellationToken);
}
public bool Wait (int millisecondsTimeout, CancellationToken cancellationToken)
{
CheckState ();
if (millisecondsTimeout < -1)
throw new ArgumentOutOfRangeException ("millisecondsTimeout",
"millisecondsTimeout is a negative number other than -1");
Stopwatch sw = Stopwatch.StartNew();
Func<bool> stopCondition = () => millisecondsTimeout >= 0 && sw.ElapsedMilliseconds > millisecondsTimeout;
do {
bool shouldWait;
int result;
do {
cancellationToken.ThrowIfCancellationRequested ();
if (stopCondition ())
return false;
shouldWait = true;
result = currCount;
if (result > 0)
shouldWait = false;
else
break;
} while (Interlocked.CompareExchange (ref currCount, result - 1, result) != result);
if (!shouldWait) {
if (result == 1)
handle.Reset ();
break;
}
SpinWait wait = new SpinWait ();
while (Thread.VolatileRead (ref currCount) <= 0) {
cancellationToken.ThrowIfCancellationRequested ();
if (stopCondition ())
return false;
if (wait.Count > spinCount) {
int diff = millisecondsTimeout - (int)sw.ElapsedMilliseconds;
int timeout = millisecondsTimeout < 0 ? deepSleepTime :
Math.Min (Math.Max (diff, 1), deepSleepTime);
handle.WaitOne (timeout);
} else
wait.SpinOnce ();
}
} while (true);
return true;
}
public WaitHandle AvailableWaitHandle {
get {
return handle;
}
}
public Task WaitAsync ()
{
return Task.Factory.StartNew (() => Wait ());
}
public Task WaitAsync (CancellationToken cancellationToken)
{
return Task.Factory.StartNew (() => Wait (cancellationToken), cancellationToken);
}
public Task<bool> WaitAsync (int millisecondsTimeout)
{
return Task.Factory.StartNew (() => Wait (millisecondsTimeout));
}
public Task<bool> WaitAsync (TimeSpan timeout)
{
return Task.Factory.StartNew (() => Wait (timeout));
}
public Task<bool> WaitAsync (int millisecondsTimeout, CancellationToken cancellationToken)
{
return Task.Factory.StartNew (() => Wait (millisecondsTimeout, cancellationToken), cancellationToken);
}
public Task<bool> WaitAsync (TimeSpan timeout, CancellationToken cancellationToken)
{
return Task.Factory.StartNew (() => Wait (timeout, cancellationToken), cancellationToken);
}
}
}
答案 3 :(得分:1)
这是解决此情况的方法:我创建了一个自定义信号量苗条类,该类使我能够增加和减少插槽数量。该类还允许我设置最大插槽数,以使我永远不会超过“合理”的数目,并且还可以设置最小插槽数,以使我不会低于“合理”的阈值。
using Picton.Messaging.Logging;
using System;
using System.Threading;
namespace Picton.Messaging.Utils
{
/// <summary>
/// An improvement over System.Threading.SemaphoreSlim that allows you to dynamically increase and
/// decrease the number of threads that can access a resource or pool of resources concurrently.
/// </summary>
/// <seealso cref="System.Threading.SemaphoreSlim" />
public class SemaphoreSlimDynamic : SemaphoreSlim
{
#region FIELDS
private static readonly ILog _logger = LogProvider.GetLogger(typeof(SemaphoreSlimDynamic));
private readonly ReaderWriterLockSlim _lock;
#endregion
#region PROPERTIES
/// <summary>
/// Gets the minimum number of slots.
/// </summary>
/// <value>
/// The minimum slots count.
/// </value>
public int MinimumSlotsCount { get; private set; }
/// <summary>
/// Gets the number of slots currently available.
/// </summary>
/// <value>
/// The available slots count.
/// </value>
public int AvailableSlotsCount { get; private set; }
/// <summary>
/// Gets the maximum number of slots.
/// </summary>
/// <value>
/// The maximum slots count.
/// </value>
public int MaximumSlotsCount { get; private set; }
#endregion
#region CONSTRUCTOR
/// <summary>
/// Initializes a new instance of the <see cref="SemaphoreSlimDynamic"/> class.
/// </summary>
/// <param name="minCount">The minimum number of slots.</param>
/// <param name="initialCount">The initial number of slots.</param>
/// <param name="maxCount">The maximum number of slots.</param>
public SemaphoreSlimDynamic(int minCount, int initialCount, int maxCount)
: base(initialCount, maxCount)
{
_lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
this.MinimumSlotsCount = minCount;
this.AvailableSlotsCount = initialCount;
this.MaximumSlotsCount = maxCount;
}
#endregion
#region PUBLIC METHODS
/// <summary>
/// Attempts to increase the number of slots
/// </summary>
/// <param name="millisecondsTimeout">The timeout in milliseconds.</param>
/// <param name="increaseCount">The number of slots to add</param>
/// <returns>true if the attempt was successfully; otherwise, false.</returns>
public bool TryIncrease(int millisecondsTimeout = 500, int increaseCount = 1)
{
return TryIncrease(TimeSpan.FromMilliseconds(millisecondsTimeout), increaseCount);
}
/// <summary>
/// Attempts to increase the number of slots
/// </summary>
/// <param name="timeout">The timeout.</param>
/// <param name="increaseCount">The number of slots to add</param>
/// <returns>true if the attempt was successfully; otherwise, false.</returns>
public bool TryIncrease(TimeSpan timeout, int increaseCount = 1)
{
if (increaseCount < 0) throw new ArgumentOutOfRangeException(nameof(increaseCount));
else if (increaseCount == 0) return false;
var increased = false;
try
{
if (this.AvailableSlotsCount < this.MaximumSlotsCount)
{
var lockAcquired = _lock.TryEnterWriteLock(timeout);
if (lockAcquired)
{
for (int i = 0; i < increaseCount; i++)
{
if (this.AvailableSlotsCount < this.MaximumSlotsCount)
{
Release();
this.AvailableSlotsCount++;
increased = true;
}
}
if (increased) _logger.Trace($"Semaphore slots increased: {this.AvailableSlotsCount}");
_lock.ExitWriteLock();
}
}
}
catch (SemaphoreFullException)
{
// An exception is thrown if we attempt to exceed the max number of concurrent tasks
// It's safe to ignore this exception
}
return increased;
}
/// <summary>
/// Attempts to decrease the number of slots
/// </summary>
/// <param name="millisecondsTimeout">The timeout in milliseconds.</param>
/// <param name="decreaseCount">The number of slots to add</param>
/// <returns>true if the attempt was successfully; otherwise, false.</returns>
public bool TryDecrease(int millisecondsTimeout = 500, int decreaseCount = 1)
{
return TryDecrease(TimeSpan.FromMilliseconds(millisecondsTimeout), decreaseCount);
}
/// <summary>
/// Attempts to decrease the number of slots
/// </summary>
/// <param name="timeout">The timeout.</param>
/// <param name="decreaseCount">The number of slots to add</param>
/// <returns>true if the attempt was successfully; otherwise, false.</returns>
public bool TryDecrease(TimeSpan timeout, int decreaseCount = 1)
{
if (decreaseCount < 0) throw new ArgumentOutOfRangeException(nameof(decreaseCount));
else if (decreaseCount == 0) return false;
var decreased = false;
if (this.AvailableSlotsCount > this.MinimumSlotsCount)
{
var lockAcquired = _lock.TryEnterWriteLock(timeout);
if (lockAcquired)
{
for (int i = 0; i < decreaseCount; i++)
{
if (this.AvailableSlotsCount > this.MinimumSlotsCount)
{
if (Wait(timeout))
{
this.AvailableSlotsCount--;
decreased = true;
}
}
}
if (decreased) _logger.Trace($"Semaphore slots decreased: {this.AvailableSlotsCount}");
_lock.ExitWriteLock();
}
}
return decreased;
}
#endregion
}
}