Interlocked.Increment on dictionary value

God*_*ent 5 .net c# 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"]);
Run Code Online (Sandbox Code Playgroud)

我知道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);
    }
}
Run Code Online (Sandbox Code Playgroud)

usr*_*usr 8

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

ConcurrentDictionary<..., StrongBox<int>> dict = ...;
Interlocked.Increment(ref dict[...].Value);
Run Code Online (Sandbox Code Playgroud)

StrongBox.Value 是一个可变的领域.


小智 7

这实际上要容易得多。特别是如果您不确定密钥是否已创建。

下面,如果存在,则将值加一,如果不存在,则使用默认值 1 创建它。Concurrents 命名空间包含构建线程安全对象所需的几乎所有内容。我通常不喜欢使用锁,因为它会序列化对对象的访问(如果我们要串行执行,为什么要执行多线程?)

ConcurrentDictionary<string, int> dataStore = new ConcurrentDictionary<string, int>();

public int Increment(string key)
{
    return dataStore.AddOrUpdate(key, 1, (k, v) => Interlocked.Increment(ref v));
}
Run Code Online (Sandbox Code Playgroud)

  • 在“updateValueFactory”委托中使用“Interlocked.Increment”是多余的。`(k, v) =&gt; v + 1` 就可以了。`v` 变量是本地变量,不能被其他线程同时访问。[`AddOrUpdate`](https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.addorupdate) 方法通过丢弃返回值并重新调用来伪造原子性委托,以防另一个线程更新特定键的速度更快。 (2认同)

Ric*_*ons 5

您使用的主要原因Interlocked是性能.如果您没有性能问题,那么如果您只是使用一个,那么您的代码对于更多人来说是可以理解的,并且更容易编写和阅读lock.

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

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

将您的计数存储在一个数组中

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

在字典中存储"计数对象"

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

编辑

使用A Sempahore

实际上,你可以研究的另一件事是使用a Semaphore.它们几乎就是为此而设计的.通过使Dictionary中的每个单元格成为一个Semaphore而不是一个计数,您可以以线程安全的方式实现非常相似的事物.你会这样做dictionary[key].WaitOne(0),如果成功则返回true,否则返回false.如果它确实返回true,那么信号量的计数已经增加,你Dictionary[hey].Release()稍后再调用.