以下帮助程序类线程是否安全?

时间:2011-10-23 16:56:17

标签: c# thread-safety

我需要跟踪顺序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;
    }

3 个答案:

答案 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);
}