我需要跟踪顺序Id,这是通过SP在4个表中执行max(id)返回给我的,db中没有管理序列的标识符/序列。这显然会出现并发问题,所以我创建了一个帮助类来确保始终生成唯一的Id。
帮助程序通过其存储库初始化,该存储库最初调用DB以查找当前Id,所有后续的Id请求都通过帮助程序在内存中提供服务。只有一个应用程序使用DB(我的)所以我不需要担心其他人出现并创建交易&使Id不同步。我认为我已经获得了线程安全的基础知识,但我担心当帮助程序初始化时的竞争条件,有人可以建议:)
private class TransactionIdProvider
{
private static readonly object Accesslock = new object();
private int _transactionId;
public int NextId
{
get
{
lock (Accesslock)
{
if(!Initialised) throw new Exception("Must Initialise with id first!!");
return _transactionId++;
}
}
}
public bool Initialised { get; private set; }
public void SetId(int id)
{
lock (Accesslock)
{
if (Initialised) return;
_transactionId = id;
Initialised = true;
}
}
public TransactionIdProvider()
{
Initialised = false;
}
}
帮助程序类在存储库中初始化:
private static readonly TransactionIdProvider IdProvider = new TransactionIdProvider();
public int GetNextTransactionId()
{
if(!IdProvider.Initialised)
{
// Ask the DB
int? id = _context.GetNextTransactionId().First();
if (!id.HasValue)
throw new Exception("No transaction Id returned");
IdProvider.SetId(id.Value);
}
return IdProvider.NextId;
}
答案 0 :(得分:8)
它是线程安全的,但它不必要地慢 你不需要锁来增加数字;相反,你可以使用原子数学。
此外,您在所有实例(static
)之间共享锁定,这是不必要的。 (一次运行两个不同的实例没有任何问题)
最后,(恕我直言)没有必要建立一个单独的未初始化状态。
我会这样写:
class TransactionIdProvider {
private int nextId;
public TransactionIdProvider(int id) {
nextId = value;
}
public int GetId() {
return Interlocked.Increment(ref nextId);
}
}
答案 1 :(得分:0)
是的,它是线程安全的;然而,IMO锁定太全局了 - 保护实例数据的静态锁定有点过分。
另外,作为属性的NextId很糟糕 - 它会改变状态,所以应该是一个方法。
您可能也更喜欢Interlocked.Increment over lock,尽管这会改变大部分类。
最后,在SetId中 - 如果它已经初始化,我会抛出异常(InvalidOperationException)而不是盲目地忽略调用 - 听起来像是一个错误。当然,这会在检查Initialized和调用SetId之间引入一个棘手的时间间隔 - 你可能只是hAve SetId如果进行了更改则返回true,如果结果是在set的位置初始化则返回false,但是SLaks的方法更好
答案 2 :(得分:0)
我认为这不是一个好主意,你应该找到另一种方法来解决这个问题。 通常当需要真正唯一的ID并且没有一种计算有效的方法来检查是否使用了id时,我会使用GUID。
但是你可以使用互锁操作而不是锁定,你可以完全没有锁定。
查找Interlocked.Increment,Interlocked.Exchange和Interlocked.CompareExchange
private class TransactionIdProvider
{
private volatile int _initialized;
private int _transactionId;
public int NextId
{
get
{
for (;;)
{
switch (_initialized)
{
case 0: throw new Exception("Not initialized");
case 1: return Interlocked.Increment(ref _transactionId);
default: Thread.Yield();
}
}
}
}
public void SetId(int id)
{
if (Interlocked.CompareExchange(ref _initialized, -1, 0) == 0)
{
Interlocked.Exchange(ref _transactionId, id);
Interlocked.Exchange(ref _initialized, 1);
}
}
}
这会给你一个警告,但这是正常的,并且在C#文档中也被报告为合法的。所以用一个很好的编译指示忽略那个警告:
// Disable warning "A reference to a volatile field will not be treated as volatile"
#pragma warning disable 0420
如果您不需要检查IsInitialized,您可以用最简单的方式完成:
public int NextId()
{
return Interlocked.Increment(ref _transactionId);
}
public void Set(int value)
{
Interlocked.Exchange(ref _transactionId, value);
}