1

多くのクライアントがアクセスするカスタム オブジェクトをホストしている 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

4 に答える 4

3

このために特別に設計されたロックReaderWriterLockSlim ( .NET 4.0 未満を使用している場合はReadWriterLock ) があります。

このロックは同時読み取りを許可しますが、書き込みが発生している場合は読み取り (およびその他の書き込み) をロックアウトします。

[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 は内部でロックを使用しますが、使用できるロックよりも寿命が短く、Microsoft が安全でない方法を使用してさらに最適化する可能性があります。内部でどのような種類のロックを使用しているか正確にはわかりません。

ただし、操作をアトミックにするために、いくつかの書き直しを行う必要があります

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

更新 2:好奇心から、私はボンネットの下で何ConcurrentDictionaryが行われているかを調べました。要素のバケットのセットをロックするだけなので、2 つのオブジェクトが同じハッシュ バケット ロックを共有している場合にのみ、ロックの競合が発生します。

通常はロック バケットがありますが、 concurrencyLevelEnvironment.ProcessorCount * 4を設定するコンストラクタを使用して手動で設定できます。

使用するロックを決定する方法は次のとおりです

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

lockCountconcurrencyLevelコンストラクターのセットと同じです。

于 2013-07-02T18:10:41.883 に答える
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、読み取りと書き込みを区別するため、書き込みが発生していない場合、読み取りは非ブロックになります。

于 2013-07-02T18:09:48.240 に答える
0

読み取りをロックする余裕がない場合は、(ロック内で) リストのコピーを作成し、それに応じて更新してから、古いリストを置き換えることができます。現在起こり得る最悪の事態は、読み取りの一部が少し古くなっていることですが、それらは決してスローされるべきではありません。

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

    this.dates = temp;
}

あまり効率的ではありませんが、あまり頻繁に実行しない場合は問題にならない可能性があります。

于 2013-07-02T18:10:58.537 に答える
0

私は同じ状況にあり、ReaderWriterLockSlim を使用して、このような状況で動作します。

于 2013-07-03T19:23:03.210 に答える