我正在尝试使用Dictionary来记录Web服务上每个API路径的当前请求数,并增加和减少当前计数我认为使用Interlocked.Increment()
的好方法是因为它会增加计算并同时读取计数。
但是下面的代码给出了一个错误,说 ref参数没有被归类为变量,我猜它是因为dict[key]
不是变量?
var dict = new Dictionary<string, int>();
dict.Add("a", 1);
int i = Interlocked.Increment(ref dict["a"]);
我知道Interlocked.Increment()
无法应用于属性,但不会想到通过密钥访问字典会产生同样的问题。
最好的方法是什么?
编辑:以下是有关此内容的更多详情。
我正在尝试编写一些代码来限制Web服务的每个API路径上的API调用数量,因此我有两个字典,即每个API路径上允许并发调用者数量的策略字典第二个计数器字典,用于记录每个API路径上当前有多少呼叫者处于活动状态。
在Web服务执行任何传入请求之前,它将检查上述两个词典,以确定请求是否应该继续,或者只是立即返回HTTP 429(Too Many Requests)响应。
以下是代码的摘录,它首先检查是否存在匹配的策略,然后如果是,则检查是否违反了最大允许请求。
public override async Task Invoke(IOwinContext context)
{
var url = context.Request.Path.ToString();
var policy = _policies.FirstOrDefault(x => x.EndpointPath == url);
if (policy != null)
{
try
{
if (Interlocked.Increment(ref _currentRequests[policy]) > policy.MaxConcurrentConnection)
{
context.Response.StatusCode = 429;
var message = string.Format(
"Max API concurrent calls quota exceeded, please try again later. Maximum admitted: {0}",
policy.MaxConcurrentConnection);
context.Response.Write(message);
context.Response.ReasonPhrase = "Too Many Requests";
}
else
{
await Next.Invoke(context);
}
}
finally
{
Interlocked.Decrement(ref _currentRequests[policy]);
}
}
else
{
await Next.Invoke(context);
}
}
答案 0 :(得分:8)
在字典中存储可变堆对象:
StrongBox.Value
"Application cannot be started, contact the application vendor."
是一个可变字段。
答案 1 :(得分:5)
使用Interlocked
的主要原因是性能。如果您没有遇到性能问题,那么只要您使用lock
,您的代码就会被更多人理解,并且更容易编写和阅读。
如果您绝对必须使用Interlocked
,那么您就无法按照您尝试的方式使用字典。 Interlocked
操作是原子的,通常在CPU级别,并且它们需要在内存中的固定位置来操作。字典上的属性访问器不提供此功能。
如果您仍想使用词典,可以考虑两种方法:
您将计数值存储在数组中。每个单元格都固定在内存中,因此可以由Interlocked
使用。字典而不是存储计数会将索引存储在存储计数的数组中。当然,您可以将这些全部写入类中,以便隐藏这些实现细节。
字典中的每个项目都是保存计数的类的实例。在课堂内部,private int
可以使用Interlocked
。您的类将提供Increment
和Decrement
方法以及只读Count
属性,以允许以类似的方式使用它。
修改强>
实际上,您可以研究的另一件事是使用Semaphore
。它们几乎是designed for this。通过使Dictionary中的每个单元格成为Semaphore
而不是计数,您可以以线程安全的方式实现非常类似的操作。你会做dictionary[key].WaitOne(0)
,如果成功则返回true,否则返回false。如果 返回true,那么该信号量的计数已经增加,您只需稍后再次调用Dictionary[hey].Release()
。
答案 2 :(得分:4)
实际上要容易得多。特别是如果您不确定是否创建密钥。
以下内容将在存在的情况下将值增加1,在不存在的情况下将其默认值创建为1。并发名称空间几乎包含构建线程安全对象所需的所有内容。我一般不喜欢使用锁,因为它会序列化对对象的访问(如果我们要串行执行,为什么要执行多线程?)
ConcurrentDictionary<string, int> dataStore = new ConcurrentDictionary<string, int>();
public int Increment(string key)
{
return dataStore.AddOrUpdate(key, 1, (k, v) => Interlocked.Increment(ref v));
}