【问题标题】:Is the ConcurrentBag<T> the appropriate collection?ConcurrentBag<T> 是合适的集合吗?
【发布时间】:2014-06-23 16:24:38
【问题描述】:

我的情况是,我在 IIS 上托管了一个 ASP.NET Web API 2 项目,并且我预计会出现并发问题。我需要生成随机数并确保它们是唯一的(稍后存储在数据库中)。为此,我实现了一个简单的内存 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();
    }
}

这段代码打算这样使用:

var number = rng.ReserveNumber();

StoreIntoDatabase(number);

rng.ReleaseNumber(number);

我是否正确使用了ConcurrentBag 集合?

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

【问题讨论】:

  • 您可能希望将InMemoryRandomNumberGenerator 变成static class。否则,您并没有真正描述您想用ConcurrentBag&lt;T&gt; 做什么。我建议您尝试一下,因为从您提供的信息中我看不出它不合适的原因。
  • @KrisVandermotten 感谢您对 Kris 的反馈。将立即应用更改。
  • 你肯定在尝试编译后意识到:静态类无法实现接口......
  • @oleksii,事实上,我自己的类中有实例字段不能使用静态(此示例已简化)。好点子。

标签: c# random concurrency asp.net-web-api thread-safety


【解决方案1】:

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

  • “当订购无关紧要时,袋子可用于存放物品,而且与集合不同,袋子支持重复。”msdn。我认为您试图避免重复。
  • 这个序列需要有某种受保护的部分或事务,否则可能会出现并发问题

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

我希望你不要推出自己的 RNG,而是重复使用 RNGCryptoServiceProvider 之类的东西。

【讨论】:

  • 我正在生成人类友好的随机数,例如 123-456-789 或 ABC。我正在使用 Random 类和一组不同的字符。我执行生成唯一字符串所需的所有验证。
  • @maxbeaudoin 我记得写了一个随机字符串扩展,也许你会have a look。那里还有其他一些替代答案。这是以防你需要一个随机字符串。
  • 我使用了一种受this post启发的非常相似的方法。你能看看我下面的回答吗?
【解决方案2】:

我已经修改了设计。正如@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));
    }
}

实施:

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());
    }
}

@oleksii 至于受保护的部分,我试图避免在序列上使用 lock 语句并改用并发集合。您能否更具体地说明以下陈述?

您需要有某种受保护的部分或事务 这个顺序,否则可能会出现并发问题

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-20
    • 1970-01-01
    • 2014-01-27
    • 1970-01-01
    相关资源
    最近更新 更多