Некоторые вопросы об использовании ConcurrentDictionary

В настоящее время я пишу приложение С#. Я новичок в использовании ConcurrentDictionary, поэтому у вас есть вопросы по безопасности потоков. Во-первых, это мой словарь:

    /// <summary>
    /// A dictionary of all the tasks scheduled
    /// </summary>
    private ConcurrentDictionary<string, ITask> tasks;

Я создаю экземпляр этого класса и использую его для отслеживания всех моих объектов, реализующих ITask. Я хочу, чтобы моя настройка работала корректно в многопоточной среде.

Если несколько потоков хотят получить подсчет количества элементов в ConcurrentDictionary, нужно ли его заблокировать?

Если я хочу получить конкретный ключ из словаря, получить объект этого ключа и вызвать метод на нем, нужно ли его заблокировать? например:

    /// <summary>
    /// Runs a specific task.
    /// </summary>
    /// <param name="name">Task name.</param>
    public void Run(string name)
    {
        lock (this.syncObject)
        {
            var task = this.tasks[name] as ITask;
            if (task != null)
            {
                task.Execute();
            }
        }
    }

Удержание нескольких потоков может вызвать метод "Выполнить", который ищет вызов метода Execute для ITask. Моя цель состоит в том, чтобы все потоки были безопасными и максимально эффективными.

Ответ 1

Методы и свойства самого ConcurrentDictionary полностью безопасны для потоков:

http://msdn.microsoft.com/en-us/library/dd287191.aspx

Представляет собой потокобезопасную коллекцию пар ключ-значение, которая может быть доступ к нескольким потокам одновременно.

Это включает свойство Count:

Граф имеет семантику моментальных снимков и представляет количество элементов в ConcurrentDictionary в тот момент, когда граф был доступ.

Однако это означает, что не означает, что объекты, хранящиеся внутри словаря, сами по себе являются потокобезопасными. То есть, нет ничего, чтобы остановить два потока от доступа к одному и тому же объекту Task и попытаться запустить на нем метод Execute. Если вам нужен последовательный (заблокированный) доступ к каждой отдельной задаче для метода Execute, то я предлагаю иметь закрытый объект внутри Task и блокировать при запуске Execute:

public class Task
{
    private object _locker = new object();

    public void Execute()
    {
        lock (_locker) {
           // code here
        }
    }
}

Это гарантирует, что по крайней мере каждая отдельная задача не имеет нескольких потоков, работающих под Execute. Я предполагаю, что это то, что вам нужно, из контекста вопроса и имен классов и методов.

Ответ 2

Все методы ConcurrentDictionary безопасны для вызова из нескольких потоков.

Это не гарантирует, что другие части вашей программы не нуждаются в синхронизации, поскольку вам может потребоваться использовать блокировки для одновременного доступа к другому объекту/нескольким объектам.

т.е. ваш образец кода блокирует и для извлечения задачи, и для ее выполнения. Он показывает пример блокировки для доступа к нескольким объектам атомарно.

Боковое примечание: вы должны просмотреть свою блокировку для task.Execute(), поскольку она запрещает другим потокам выполнять задачу параллельно. Это может быть то, чего вы хотите достичь, но в этом случае использование ConcurrentDictionary может быть излишним.