【发布时间】:2015-11-18 09:15:00
【问题描述】:
我正在尝试使用字典来记录 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(请求过多)响应。
这是代码的摘录,它首先检查是否有匹配的策略,如果有,然后检查是否超出了允许的最大请求数。
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);
}
}
【问题讨论】:
-
你想做什么?即使此代码有效,它也不能保证存储值的线程安全修改。它只会保证返回的副本的线程安全。也许您正在寻找 ConcurrentDictionary 或不可变类之一?
-
@PanagiotisKanavos ,为什么不能保证字典中值的线程安全修改..我希望 Interlocked.Increment (参考某些东西)将确保不同的线程可以修改和读取的值安全的东西。我将编辑问题以提供更多详细信息。谢谢。
-
尝试使用
lock语句而不是Interlocked -
简单地说。字典是通过属性获取完成的,因此您不能对其值进行互锁的操作。但是,您可以对数组单元执行互锁操作 - 它们是直接访问的固定内存位置。因此,您将设置一个大数组,然后您的字典将返回该数组的索引,而不是返回计数本身,然后将计数存储在数组中。然后你可以对数组单元进行互锁操作。当然,您可以将其封装在一个类中,这样使用该类就与使用字典一样。
-
另一种方法是字典中的每个项目都是一个“计数”对象。您调用这些对象上的方法来递增、递减和获取当前值。在内部,这些对象使用 Interlocked 来递增和递减它们的私有
int,使用属性访问器以只读方式访问。
标签: c# .net multithreading web-services