Interlocked.Increment on dictionary value

时间:2015-11-18 09:15:01

标签: c# .net multithreading web-services

我正在尝试使用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);
    }
}

3 个答案:

答案 0 :(得分:8)

在字典中存储可变堆对象:

StrongBox.Value

"Application cannot be started, contact the application vendor." 是一个可变字段。

答案 1 :(得分:5)

使用Interlocked的主要原因是性能。如果您没有遇到性能问题,那么只要您使用lock,您的代码就会被更多人理解,并且更容易编写和阅读。

如果您绝对必须使用Interlocked,那么您就无法按照您尝试的方式使用字典。 Interlocked操作是原子的,通常在CPU级别,并且它们需要在内存中的固定位置来操作。字典上的属性访问器不提供此功能。

如果您仍想使用词典,可以考虑两种方法:

将您的计数存储在数组

您将计数值存储在数组中。每个单元格都固定在内存中,因此可以由Interlocked使用。字典而不是存储计数会将索引存储在存储计数的数组中。当然,您可以将这些全部写入类中,以便隐藏这些实现细节。

在字典中存储“count objects”

字典中的每个项目都是保存计数的类的实例。在课堂内部,private int可以使用Interlocked。您的类将提供IncrementDecrement方法以及只读Count属性,以允许以类似的方式使用它。

修改

使用Sempahore

实际上,您可以研究的另一件事是使用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));
}