C# 是ConcurrentBag<;T>;合适的收藏?

C# 是ConcurrentBag<;T>;合适的收藏?,c#,random,concurrency,asp.net-web-api,thread-safety,C#,Random,Concurrency,Asp.net Web Api,Thread Safety,我遇到的情况是,我有一个ASP.NET Web API 2项目托管在IIS上,我预计会出现并发问题。我需要生成随机数并确保它们是唯一的(稍后存储在数据库中)。为此,我实现了一个简单的内存中RNG,它依赖于静态ConcurrentBag。我知道这种实现会给分布式体系结构带来风险。很快,代码如下所示: public interface IRandomNumberGenerator { string ReserveNumber(); string ReleaseNumber(strin

我遇到的情况是,我有一个ASP.NET Web API 2项目托管在IIS上,我预计会出现并发问题。我需要生成随机数并确保它们是唯一的(稍后存储在数据库中)。为此,我实现了一个简单的内存中RNG,它依赖于静态ConcurrentBag。我知道这种实现会给分布式体系结构带来风险。很快,代码如下所示:

public interface IRandomNumberGenerator
{
    string ReserveNumber();
    string ReleaseNumber(string number);
}

public class InMemoryRandomNumberGenerator : IRandomNumberGenerator
{
    private static readonly ConcurrentBag<string> Bag = new ConcurrentBag<string>();

    public string ReserveNumber()
    {
        // Add
        throw new NotImplementedException();
    }

    public string ReleaseNumber(string number)
    {
        // Remove
        throw new NotImplementedException();
    }
}
我是否正确使用了
ConcurrentBag
集合


还请注意,我简化了我的示例,我对将代码移动到SQL中并使用SQL事务来完成此任务不感兴趣。

我猜您正在尝试解决一个并发问题,其中许多用户单击按钮生成一个数字。虽然从并发角度来看,
ConcurrentBag
可能可以使用,但我看到了其他问题:

  • “当排序无关紧要时,袋子对于存储对象很有用,而且与集合不同,袋子支持重复。”。我想你是想避免重复
  • 对于这个序列,您需要有某种受保护的节或事务,否则可能会出现并发问题

    var number = rng.ReserveNumber();
    StoreIntoDatabase(number);
    rng.ReleaseNumber(number);
    

我希望您不要推出自己的RNG,而是重用类似的东西。

我已经修改了设计。正如@oleksii所指出的,我切换到了
ConcurrentDictionary
,以避免重复。我使用字节是因为我不使用该值,而且据我所知没有
ConcurrentHashset

NUnit测试:

[Test]
public void GenerateStrings()
{
    var gen1 = new ConcurrentStringGenerator("0123456789", 9);

    for (int i = 0; i < 100; i++)
    {
        var str = gen1.Reserve();
        Console.WriteLine(int.Parse(str).ToString("000-000-000"));
        Assert.True(gen1.Release(str));
    }

    var gen2 = new ConcurrentStringGenerator("ABCDEFGHJKLMNPQRSTUVWXYZ", 3);

    for (int i = 0; i < 100; i++)
    {
        var str = gen2.Reserve();
        Console.WriteLine(str);
        Assert.True(gen2.Release(str));
    }
}
[测试]
公营部门开支()
{
var gen1=新的ConcurrentStringGenerator(“0123456789”,9);
对于(int i=0;i<100;i++)
{
var str=gen1.Reserve();
Console.WriteLine(int.Parse(str.ToString)(“000-000-000”);
Assert.True(gen1.Release(str));
}
var gen2=新的ConcurrentStringGenerator(“ABCDEFGHJKLMNPNPQRSTUVWXYZ”,3);
对于(int i=0;i<100;i++)
{
var str=gen2.Reserve();
控制台写入线(str);
Assert.True(gen2.Release(str));
}
}
实施:

public class ConcurrentStringGenerator
{
    private readonly Random _random;
    private readonly string _charset;
    private readonly int _length;
    private readonly ConcurrentDictionary<string, byte> _numbers;

    public ConcurrentStringGenerator(string charset, int length)
    {
        _charset = charset;
        _length = length;
        _random = new Random();
        _numbers = new ConcurrentDictionary<string, byte>();
    }

    public string Reserve()
    {
        var str = Generate();
        while (!_numbers.TryAdd(str, 0))
        {
            str = Generate();
        }
        return str;
    }

    public bool Release(string str)
    {
        byte b;
        return _numbers.TryRemove(str, out b);
    }

    private string Generate()
    {
        return new string(Enumerable.Repeat(_charset, _length).Select(s => s[_random.Next(s.Length)]).ToArray());
    }
}
公共类ConcurrentStringGenerator
{
私有只读随机\u随机;
私有只读字符串\u字符集;
私有只读整数长度;
专用只读ConcurrentDictionary\u编号;
公共ConcurrentStringGenerator(字符串字符集,整数长度)
{
_字符集=字符集;
_长度=长度;
_随机=新随机();
_数字=新的ConcurrentDictionary();
}
公共字符串保留()
{
var str=Generate();
而(!_number.TryAdd(str,0))
{
str=Generate();
}
返回str;
}
公共布尔释放(字符串str)
{
字节b;
返回_number.TryRemove(str,out b);
}
私有字符串生成()
{
返回新字符串(可枚举。重复(_字符集,_长度)。选择(s=>s[_random.Next(s.length)]).ToArray();
}
}
@oleksii至于protected部分,我试图避免在序列上使用lock语句,而是使用并发集合。你能更具体地谈谈下面的陈述吗

您需要有某种类型的受保护分区或事务 按此顺序,否则可能会出现并发问题

var number = rng.ReserveNumber();
StoreIntoDatabase(number);
rng.ReleaseNumber(number);

您可能希望将MemoryRandomNumberGenerator中的
转换为
静态类
。否则,您并不是在真正描述您想用
ConcurrentBag
做什么。我建议你试试看,因为从你提供的信息来看,我看不出有什么理由不合适。@KrisVandermotten谢谢你的反馈Kris。将立即应用更改。在尝试编译之后,您肯定意识到:静态类无法实现接口…@oleksii,事实上,我自己的类中有实例字段,不能使用静态(此示例已简化)。很好。我正在生成人性化的随机数,比如123-456-789或ABC。我使用的是随机类和一组不同的字符。我执行生成唯一字符串所需的所有验证。@maxbeaudoin我记得我写了一个随机字符串扩展名,也许你会这么做。还有一些其他的备选答案。这是为了防止您需要一个随机字符串。我使用了一个非常类似的方法,灵感来自。你能看看我下面的答案吗?