1

コードをマルチ読み取り可能にしたいので、辞書をConcurrentDictionaryに変更する必要があります。ConcurrentDictionaryについて読み、いくつかの例を確認しましたが、それでもこれを理解する必要があります。

これが元のコードです(シングルスレッドの場合)

private IDictionary<string, IDictionary<string, Task>> _tasks;
public override IDictionary<string, IDictionary<string, Task>> Tasks
    {
        get
        {
            // return dictionary from cache unless too old
            // concurrency!! (null check)
            if (_tasks != null && (DateTime.Now - _lastTaskListRefreshDateTime < TimeSpan.FromSeconds(30)))
            {
                return _tasks;
            }

            // reload dictionary from database
            _tasks = new Dictionary<string, IDictionary<string, Task>>();

            // find returns an IEnumerable<Task>
            var tasks = Find<Task>(null, DependencyNode.TaskForCrawler).Cast<Task>();

            // build hierarchical dictionary from flat IEnumerable
            // concurrency!!
            foreach (var t in tasks)
            {

                if (_tasks.ContainsKey(t.Area.Key))
                {
                    if (_tasks[t.Area.Key] == null)
                    {
                        _tasks[t.Area.Key] = new Dictionary<string, Task>();
                    }

                    if (!_tasks[t.Area.Key].ContainsKey(t.Key))
                    {
                        _tasks[t.Area.Key].Add(t.Key, t);
                    }
                }
                else
                {
                    _tasks.Add(t.Area.Key, new Dictionary<string, Task> { { t.Key, t } });
                }
            }

            _lastTaskListRefreshDateTime = DateTime.Now;
            return _tasks;
        }

        set
        {
            _tasks = value;
        }
    }

これが私が思いついたものです:

private ConcurrentDictionary<string, ConcurrentDictionary<string, Task>> _tasks = new ConcurrentDictionary<string, ConcurrentDictionary<string, Task>>();
public override ConcurrentDictionary<string, ConcurrentDictionary<string, Task>> Tasks
{
        get
        {
            // use cache
            // concurrency?? (null check)
            if (!_tasks.IsEmpty && (DateTime.Now - _lastTaskListRefreshDateTime < TimeSpan.FromSeconds(30)))
            {
                return _tasks;
            }

            // reload
            var tasks = Find<Task>(null, DependencyNode.TaskForCrawler).Cast<Task>();

            foreach (var task in tasks)
            {
                var t = task; // inner scope for clousure
                var taskKey = t.Key;
                var areaKey = t.Area.Key;

                var newDict = new ConcurrentDictionary<string, Task>();
                newDict.TryAdd(taskKey, t);

                _tasks.AddOrUpdate(areaKey, newDict, (k, v) => {
                                                        // An dictionary element if key=areaKey already exists
                                                        // extend and return it.
                                                        v.TryAdd(taskKey, t);
                                                        return v;
                                                       });
            }

            _lastTaskListRefreshDateTime = DateTime.Now;
            return _tasks;
        }

これが正しいかどうかはわかりません。特に、IsEmptyチェックは、チェックとパーツまたはパーツ_tasksの間で初期化されている可能性があるため、スレッドセーフではないと確信しています。このチェックを手動でロックする必要がありますか?ダブルロック(ヌルチェック>ロック>ヌルチェック)が必要ですか?IsEmpty&& ...return _tasks

4

2 に答える 2

1

あなたの懸念は正当化されます。Tasksプロパティゲッターはスレッドセーフではありません。ここにはいくつかの問題があります。

まず、あなたの側と同じように、あるIsEmptyスレッドからの呼び出しと別のスレッドからのアイテムの削除の間には競合があります。ゲッターは空の辞書を返す可能性があります。

_lastTaskListRefreshDateTime第二に、ifチェックでの読み取りとゲッターの最後での割り当てとの間に競合があります。これらの操作がアトミックである場合でも(64ビットであるため少なくとも32ビットプラットフォームでは実行できませんDateTime)、コードにそのような同期メカニズムがないため、微妙なメモリバリアの問題volatileがあります。

第三に、上記の私の説明と同様に、参照に関する別のメモリバリアの問題があり_tasksます。あるスレッドがセッターを呼び出し、別のスレッドがゲッターを呼び出すことができます。メモリバリアが存在しないため、CLRまたはハードウェアは、セッターで行われた変更がゲッターに表示されないように、読み取りと書き込みを自由に最適化できます。この問題は必ずしも問題を引き起こすとは限りませんが、予期しない動作であると思います。分析のための他の文脈がなければ、私はどちらの方法も言うことができません。

于 2011-05-18T18:34:59.503 に答える
1

辞書のConcurrentDictionary読み取りと書き込みが互いに歩き回らないことを保証する唯一の方法は、Dictionaryクラスが行わないことです。のスレッドConcurrentDictionaryセーフは、コードをスレッドセーフにするものではなく、コードがスレッドセーフであることを保証するだけです。これが事実であるため、ゲッターにロックが必要になります。

于 2011-05-18T20:50:41.023 に答える