1

我想让我的代码具有多线程可读性,因此我需要将 Dictionary 更改为 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

唯一保证对字典的ConcurrentDictionary读取和写入不会相互交叉,这是Dictionary类不做的事情。中的线程安全ConcurrentDictionary不会使您的代码线程安全,它只确保其代码是线程安全的。既然是这种情况,您将需要锁定您的吸气剂。

于 2011-05-18T20:50:41.023 回答
1

你的担心是有道理的。Tasks属性 getter 不是线程安全的。这里有几个问题。

IsEmpty首先,像您一样,从一个线程调用与从另一个线程删除项目之间存在竞争。getter 可以返回一个空字典。

_lastTaskListRefreshDateTime其次,在if检查中的读取和 getter 末尾的赋值之间存在竞争。即使这些操作是原子的(它们至少不能在 32 位平台上,因为DateTime是 64 位)仍然存在一个微妙的内存屏障问题,因为volatile在代码中没有明显的同步机制。

第三,与我上面的解释类似,还有一个内存屏障问题与_tasks参考有关。一个线程可以调用setter,而另一个线程调用getter。由于不存在内存屏障,CLR 或硬件可以自由地优化读取和写入,以使在 setter 中所做的更改对 getter 不可见。这个问题可能不一定会导致任何问题,但我敢打赌,这是没有预料到的行为。在没有其他分析背景的情况下,我不能说任何一种方式。

于 2011-05-18T18:34:59.503 回答