C# 获取下一个值I';在我需要背景线程之前,我需要它们

C# 获取下一个值I';在我需要背景线程之前,我需要它们,c#,multithreading,C#,Multithreading,我希望能找到一些建议,告诉我在需要之前获取一堆id值(比如数据库标识值)的最佳方法。我有许多类需要唯一的id(int),我想做的是获取下一个可用的id(每个类,每个服务器),并在本地缓存它。当一个身份证被采取,我想得到下一个准备等 我已经编写了一些代码来演示我要做的事情。代码很糟糕(它应该包含锁等),但我认为它能说明问题。丢失奇数id不是问题-重复id是(问题)。我对GetNextiAsync的胆量很满意——它调用一个proc this.Database.SqlQuery<int>(

我希望能找到一些建议,告诉我在需要之前获取一堆id值(比如数据库标识值)的最佳方法。我有许多类需要唯一的id(int),我想做的是获取下一个可用的id(每个类,每个服务器),并在本地缓存它。当一个身份证被采取,我想得到下一个准备等

我已经编写了一些代码来演示我要做的事情。代码很糟糕(它应该包含锁等),但我认为它能说明问题。丢失奇数id不是问题-重复id是(问题)。我对GetNextiAsync的胆量很满意——它调用一个proc

this.Database.SqlQuery<int>("EXEC EntityNextIdentityValue @Key", 
            new SqlParameter("Key", key))).First();
this.Database.SqlQuery(“EXEC EntityNextIdentityValue@Key”,
新的SqlParameter(“Key”,Key))).First();
在SQL Server上,使用sp_getapplock确保每个返回值都是唯一的(并且是增量的)

静态类ClassId
{
静态专用字典_id=new Dictionary();
静态专用字典_threads=new Dictionary();
静态ClassId()
{
//获取所有已知类的第一个NextId
StartGetNextId(“类别1”);
StartGetNextId(“第2类”);
StartGetNextId(“第3类”);
}
静态公共int NextId(字符串键)
{
//等待nextId的当前呼叫完成
而(_threads.ContainsKey(key)){}
//获取当前的nextId
int nextId=_id[key];
//开始下一个星期的通话
StartGetNextId(钥匙);
//返回当前的nextId
返回nextId;
}
静态私有void StartGetNextId(字符串键)
{
_添加(key,新线程(()=>GetNextIdAsync(key));
_线程[key].Start();
}
静态私有void GetNextIdAsync(字符串键)
{
//调用长时间运行的任务以获取下一个可用值
睡眠(1000);
如果(_id.ContainsKey(key))_id[key]+=1;
else _id.Add(键,1);
_螺纹。移除(键);
}
}

我的问题是——在我需要下一个价值之前,最好的方法是什么?课程应该如何安排,锁应该放在哪里?例如,在GetNextIdAsync()中锁定添加新线程但不启动它,并将StartGetNextId()更改为call.start()?

您应该让数据库通过适当标记该列来生成标识值。您可以使用或类似的方法检索该值


实现的主要失败是NextId中的繁忙等待和从多个线程同时访问字典。最简单的解决方案是使用下面ohadsc建议的BlockingCollection。您需要预测数据库宕机而无法获得更多id的情况—您不想使应用程序死锁。因此,您需要使用Take()重载来接受ConcellationToken,在访问数据库失败时会通知它。

这似乎是生产者-消费者模式的一个好应用程序

我的想法是:

private ConcurrentDictionary<string, int> _ids;
private ConcurrentDictionary<string, Thread> _threads;
private Task _producer;
private Task _consumer;
private CancellationTokenSource _cancellation;

private void StartProducer()
{
    _producer = Task.Factory.StartNew(() => 
       while (_cancellation.Token.IsCancellationRequested == false)
       {
           _ids.Add(GetNextKeyValuePair());
       }
   )
}

private void StartConsumer()
{
    _consumer = Task.Factory.StartNew(() => 
       while (_cancellation.Token.IsCancellationRequested == false)
       {
           UseNextId(id);
           _ids.Remove(id);
       }
   )
}
私有ConcurrentDictionary\u id;
私有ConcurrentDictionary_线程;
私有任务生产者;
私人任务(用户),;
私有取消令牌源\u取消;
私有void StartProducer()
{
_producer=Task.Factory.StartNew(()=>
while(_cancellation.Token.IsCancellationRequested==false)
{
_Add(GetNextKeyValuePair());
}
)
}
私有void StartConsumer()
{
_consumer=Task.Factory.StartNew(()=>
while(_cancellation.Token.IsCancellationRequested==false)
{
UseNextId(id);
_id.Remove(id);
}
)
}
有几件事需要指出

首先,您可能已经知道这一点,使用线程安全集合(如
ConcurrentDictionary
BlockingCollection
)而不是普通的
Dictionary
List
)非常重要。如果你不这样做,坏事就会发生,人们会死,婴儿会哭

其次,您可能需要一些比基本的
CancellationTokenSource
稍微便宜一点的东西,这正是我在服务编程中所习惯的。关键是要有办法取消这些东西,这样你就可以优雅地关闭它们

第三,考虑抛出<代码>睡眠> /代码> s,以避免它过于苛刻地处理处理器。


这方面的细节取决于你能以多快的速度生成这些东西,而不是你能以多快的速度消费它们。如果消费者的运行速度远远高于生产者,我的代码绝对不能保证你在消费者要求之前就拥有你想要的ID。然而,这是一种不错的方法,尽管是同时组织准备此类数据的基本方法。

为此,您可以使用
BlockingCollection
。基本上,您将有一个线程将新ID泵入缓冲区:

BlockingCollection<int> _queue = new BlockingCollection<int>(BufferSize);

void Init()
{
    Task.Factory.StartNew(PopulateIdBuffer, TaskCreationOptions.LongRunning);
}

void PopulateIdBuffer()
{
    int id = 0;
    while (true)
    {
        Thread.Sleep(1000); //Simulate long retrieval
        _queue.Add(id++);
    }
}

void SomeMethodThatNeedsId()
{
    var nextId = _queue.Take();
    ....
}
BlockingCollection\u queue=newblockingcollection(BufferSize);
void Init()
{
Task.Factory.StartNew(PopulateIdBuffer,TaskCreationOptions.LongRunning);
}
void PopulateIdBuffer()
{
int id=0;
while(true)
{
Thread.Sleep(1000);//模拟长检索
_添加(id++);
}
}
void somemethodthaneedsid()
{
var nextId=_queue.Take();
....
}

是否有理由在后台线程中获取值?另外,在需要之前生成ID的原因是什么?为什么不准确地在需要时生成它们呢?我想让它们提前发挥性能-在需要下一个id之前进行数据库调用,而不是在我需要时等待。请注意,如果在项目存在之前需要id,只需创建项目即可请不要立即填充数据。对不起,我犯了错误-我对函数GetNextIdAsync很满意-我的问题是关于threadin的
BlockingCollection<int> _queue = new BlockingCollection<int>(BufferSize);

void Init()
{
    Task.Factory.StartNew(PopulateIdBuffer, TaskCreationOptions.LongRunning);
}

void PopulateIdBuffer()
{
    int id = 0;
    while (true)
    {
        Thread.Sleep(1000); //Simulate long retrieval
        _queue.Add(id++);
    }
}

void SomeMethodThatNeedsId()
{
    var nextId = _queue.Take();
    ....
}