Services中的线程安全设置为ConcurrencyMode.Multiple

时间:2013-07-02 18:01:06

标签: c# multithreading wcf

我正在开发一个WCF应用程序,该应用程序托管一个自定义对象供许多客户端访问。它基本上工作,但因为我需要处理数以千计的并发客户端,我需要该服务能够处理并发读取调用(更新将很少)。我通过在更新对象时锁定私有字段来添加一些线程安全性。

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public sealed class TestServerConfig : ConfigBase, ITestInterface
{
    private object updateLock = new object();

    private SortedList<string, DateTime> dates = new SortedList<string, DateTime>();

    public DateTime GetDate(string key)
    {
        if (this.dates.ContainsKey(key))
        {
            return this.dates[key];
        }
        else
        {
            return DateTime.MinValue;
        }
    }
    public void SetDate(string key, DateTime expirationDate)
    {
        lock (this.updateLock)
        {
            if (this.dates.ContainsKey(key))
            {
                this.dates[key] = expirationDate;
            }
            else
            {
                this.dates.Add(key, expirationDate);
            }
        }
    }
}

我的问题是如何在没有锁定的情况下使GetDate线程安全,以便可以执行对GetDate的并发调用,但是当检查后但在读取值之前删除集合中的值时,异常不会随机发生。

抓住异常并处理它是可能的,但我更愿意将它发现。

有什么想法吗?

4 个答案:

答案 0 :(得分:3)

有一个专门为此设计的锁,ReaderWriterLockSlimReadWriterLock如果使用的是低于.NET 4.0)

此锁允许并发读取,但在发生写入时会锁定读取(和其他写入)。

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public sealed class TestServerConfig : ConfigBase, ITestInterface
{
    private ReaderWriterLockSlim updateLock = new ReaderWriterLockSlim();

    private SortedList<string, DateTime> dates = new SortedList<string, DateTime>();


    public DateTime GetDate(string key)
    {
        try
        {   
            this.updateLock.EnterReadLock();
            if (this.dates.ContainsKey(key))
            {
                return this.dates[key];
            }
            else
            {
                return DateTime.MinValue;
            }
        }
        finally
        {
            this.updateLock.ExitReadLock();
        }
    }
    public void SetDate(string key, DateTime expirationDate)
    {
        try
        {
            this.updateLock.EnterWriteLock();

            if (this.dates.ContainsKey(key))
            {
                this.dates[key] = expirationDate;
            }
            else
            {
                this.dates.Add(key, expirationDate);
            }
        }
        finally
        {
             this.updateLock.ExitWriteLock();
        }
    }
}

还有支持超时的“Try”版本的锁,您只需检查返回的bool以查看是否已锁定。


更新:另一个解决方案是使用ConcurrentDictionary,这根本不需要任何锁定。 ConcurrentDictionary在内部使用锁,但它们比你可以使用的更短,也有可能微软可以使用某种形式的不安全方法来进一步优化它,我不知道它们究竟在内部采用什么类型的锁。

你需要做一些重写,以使你的操作成为原子

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public sealed class TestServerConfig : ConfigBase, ITestInterface
{
    private ConcurrentDictionary<string, DateTime> dates = new ConcurrentDictionary<string, DateTime>();

    public DateTime GetDate(string key)
    {
        DateTime result;

        if (this.dates.TryGetValue(key, out result))
        {
            return result;
        }
        else
        {
            return DateTime.MinValue;
        }
    }
    public void SetDate(string key, DateTime expirationDate)
    {
        this.dates.AddOrUpdate(key, expirationDate, (usedKey, oldValue) => expirationDate);
    }
}

UPDATE2:出于好奇,我在引擎盖下看看ConcurrentDictionary做了什么,它做的只是锁定元素的一组桶,所以你只能得到如果两个对象也共享相同的哈希桶锁,则会发生锁争用。

通常有Environment.ProcessorCount * 4个锁定桶,但您可以使用设置concurrencyLevel的构造函数手动设置它。

以下是决定使用哪个锁的方法

private void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount)
{
    bucketNo = (hashcode & 2147483647) % bucketCount;
    lockNo = bucketNo % lockCount;
}

lockCount等于构造函数中设置的concurrencyLevel

答案 1 :(得分:1)

我建议您使用ReaderWriterLockSlim文档提供一个几乎完全符合您要求的示例。 (http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx

但是,这样的事情:

public DateTime GetDate(string key)
{
    cacheLock.EnterReadLock();
    try
    {
        if (this.dates.ContainsKey(key))
        {
            return this.dates[key];
        }
        else
        {
            return DateTime.MinValue;
        }
    }
    finally
    {
        cacheLock.ExitReadLock();
    }
}
public void SetDate(string key, DateTime expirationDate)
{
    cacheLock.EnterWriteLock();
    try
    {
        if (this.dates.ContainsKey(key))
        {
            this.dates[key] = expirationDate;
        }
        else
        {
            this.dates.Add(key, expirationDate);
        }
    }
    finally
    {
        cacheLock.ExitWriteLock();
    }
}

ReaderWriterLockSlim比使用lock更高效,并区分读写,因此如果没有写入,则读取变为非阻塞。

答案 2 :(得分:0)

如果您确实无法承受锁定读取,您可以(在锁内)制作列表的副本,相应地更新它,然后替换旧列表。现在可能发生的最糟糕的事情是,有些读取会有点过时,但它们永远不会抛出。

lock (this.updateLock)
{
    var temp = <copy list here>
    if (temp.ContainsKey(key))
    {
        temp[key] = expirationDate;
    }
    else
    {
        temp.Add(key, expirationDate);
    }

    this.dates = temp;
}

效率不高,但如果你不经常这样做,那可能无关紧要。

答案 3 :(得分:0)

我遇到了同样的情况,并使用了ReaderWriterLockSlim并处理了这种情况。