У меня Dictionary<string, someobject>.
Обновлено: Мне было указано, что мой пример был плохим. Все мое намерение состояло не в том, чтобы обновлять ссылки в цикле, а в том, чтобы обновить разные значения на основе разных потоков, которые необходимо обновить / получить данные. Я изменил цикл на метод.
Мне нужно обновить элементы в моем словаре - по одному ключу за раз, и мне было интересно, есть ли проблемы с использованием блокировки значения .key моего объекта Dictionary?
private static Dictionary<string, MatrixElement> matrixElements = new Dictionary<string, MatrixElement>();
//Pseudo-code
public static void UpdateValue(string key)
{
KeyValuePair<string, MatrixElement> keyValuePair = matrixElements[key];
lock (keyValuePair.Key)
{
keyValuePair.Value = SomeMeanMethod();
}
}
Это выдержит суд или потерпит неудачу? Я просто хочу, чтобы каждое значение в словаре было независимо заблокировано, поэтому блокировка (и обновление) одного значения не блокирует другие. Также я знаю, что блокировка будет удерживаться в течение длительного времени, но данные будут недействительными до тех пор, пока не будут полностью обновлены.
На самом деле я пробовал, но значение доступно только для чтения, или так мне сказала моя IDE?
вы не можете изменять элементы foreach
Где именно параллелизм, которого вы хотите избежать? Если вы запустите свой цикл в несколько потоков, первый блокирует первую клавишу, все остальные блокируются при нажатии первой клавиши. Итак, я не понимаю сценарий, который вы хотите решить. Не могли бы вы прояснить свой вопрос по этому поводу?
TToni: Вы абсолютно правы, но мой пример здесь упрощен - они не будут обновляться в цикле - это был просто плохой пример, который я сделал - каждый поток может обновлять разные значения - или то же самое, чего я бы хотел избежать.
Вот почему я попросил прояснить вопрос :-) Идет много дискуссий, которые сосредотачиваются на том, почему цикл не работает, что вы аннулируете любой перечислитель коллекции, обновляя его, и так далее. Так что да, плохой пример, пожалуйста, выберите лучший.
Аналогичный вопрос: stackoverflow.com/questions/168249/…





Нет, это не сработает.
Причина - интернирование струн. Это означает, что:
string a = "Something";
string b = "Something";
оба являются одним и тем же объектом! Следовательно, вы никогда не должны блокировать строки, потому что, если какая-то другая часть программы (например, другой экземпляр того же объекта) также захочет заблокировать ту же строку, вы можете случайно создать конфликт блокировок там, где в этом нет необходимости; возможно даже тупик.
Однако не стесняйтесь делать это с не-строками. Для большей ясности я взял в личную привычку всегда создавать отдельный объект блокировки:
class Something
{
bool threadSafeBool = true;
object threadSafeBoolLock = new object(); // Always lock this to use threadSafeBool
}
Я рекомендую вам поступить так же. Создайте Словарь с объектами блокировки для каждой ячейки матрицы. Затем при необходимости заблокируйте эти объекты.
PS. Изменение коллекции, которую вы повторяете, считается не очень приятным. Это даже вызовет исключение для большинства типов коллекций. Попробуйте реорганизовать это - например, перебирать список ключей, если он всегда будет постоянным, а не пары.
Вы также можете отключить интернирование строк - я считаю, что это делают все сборки .NET framework.
«Ключом» к словарю может быть любой объект, а не только строка! Это не применимо, если бы ключ был целым числом!
Важно отметить, что неявно интернируются только строковые литералы. Для других строк необходимо использовать string.Intern (). Я полностью согласен с вашей точкой зрения, что строки небезопасны для этого типа вещей, потому что любая данная строка может быть интернирована, что может вызвать небольшие проблемы с блокировкой.
Блокировка объекта, доступного вне кода блокировки, представляет собой большой риск. Если какой-либо другой код (где угодно) когда-либо блокирует этот объект, вы можете попасть в тупиковые ситуации, которые трудно отлаживать. Также обратите внимание, что вы блокируете объект, а не ссылку, поэтому, если я дам вам словарь, я все еще могу удерживать ссылки на ключи и блокировать их, что заставляет нас блокировать один и тот же объект.
Если вы полностью инкапсулируете словарь и сами генерируете ключи (они никогда не передаются, тогда вы можете быть в безопасности.
Однако старайтесь придерживаться одного правила - по возможности ограничивайте видимость объектов, которые вы блокируете, самим кодом блокировки.
Вот почему вы видите это:
public class Something
{
private readonly object lockObj = new object();
public SomethingReentrant()
{
lock(lockObj) // Line A
{
// ...
}
}
}
вместо того, чтобы видеть строку A выше, замененную на
lock(this)
Таким образом, отдельный объект будет заблокирован, а видимость будет ограничена.
РедактироватьДжон Скит правильно заметил, что lockObj выше должен быть доступен только для чтения.
Я бы добавил, что lockObj также почти всегда следует объявлять только для чтения.
В вашем примере вы не можете делать то, что хотите!
Вы получите System.InvalidOperationException с сообщением Коллекция была изменена; операция перечисления может не выполняться.
Вот пример, подтверждающий:
using System.Collections.Generic;
using System;
public class Test
{
private Int32 age = 42;
static public void Main()
{
(new Test()).TestMethod();
}
public void TestMethod()
{
Dictionary<Int32, string> myDict = new Dictionary<Int32, string>();
myDict[age] = age.ToString();
foreach(KeyValuePair<Int32, string> pair in myDict)
{
Console.WriteLine("{0} : {1}", pair.Key, pair.Value);
++age;
Console.WriteLine("{0} : {1}", pair.Key, pair.Value);
myDict[pair.Key] = "new";
Console.WriteLine("Changed!");
}
}
}
Результатом будет:
42 : 42
42 : 42
Unhandled Exception: System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Enumerator.MoveNext()
at Test.TestMethod()
at Test.Main()
Я не хочу блокировать весь словарь - только значение keypairvalue - поэтому никакие два процесса не будут обновлять одно и то же значение в keypairvalue за раз.
Я не меняю ключ - только значение, так что проблем быть не должно.
Я вижу здесь несколько потенциальных проблем:
matrixElements[someKey].ChangeAllYourContents().Примечание: я предполагаю, что исключение при изменении коллекции во время итерации уже исправлено.
Dictionary не является потокобезопасной коллекцией, что означает, что нет безопасно изменять и читать коллекцию из разных потоков без внешней синхронизации. Hashtable является (был?) Потокобезопасным для сценария с одним писателем и многими читателями, но Dictionary имеет другую внутреннюю структуру данных и не наследует эту гарантию.
Это означает, что вы не можете изменить свой словарь, когда вы обращаетесь к нему для чтения или записи из другого потока, он может просто нарушить внутренние структуры данных. Блокировка ключа не защищает внутреннюю структуру данных, потому что, пока вы изменяете этот самый ключ, кто-то может читать другой ключ вашего словаря в другом потоке. Даже если вы можете гарантировать, что все ваши ключи являются одними и теми же объектами (как сказано об интернировании строк), это не принесет вам никакой безопасности. Пример:
Если в словаре нет такого ключа, первый поток начнет изменять список, а второй поток, скорее всего, прочитает неполное состояние.
Если такой ключ уже существует, детали реализации словаря могут по-прежнему изменять структуру данных, например перемещать недавно использованные ключи в начало списка для более быстрого поиска. Вы не можете полагаться на детали реализации.
Таких случаев, когда вы испортили словарь, очень много. Таким образом, у вас должен быть внешний объект синхронизации (или использовать сам словарь, если он не открыт для публики) и заблокировать его во время всей операции. Если вам нужны более детализированные блокировки, когда операция может занять некоторое время, вы можете скопировать ключи, которые вам нужно обновить, перебрать их, заблокировать весь словарь во время обновления одного ключа (не забудьте проверить, что ключ все еще существует) и отпустить его, чтобы позвольте другим потокам работать.
Если я не ошибаюсь, первоначальное намерение состояло в том, чтобы заблокировать один элемент, а не весь словарь (например, блокировка на уровне таблицы или блокировка на уровне строки в БД)
вы не можете заблокировать ключ словаря, как многие здесь объясняли.
Что вы можете сделать, так это сохранить внутренний словарь объектов блокировки, который соответствует фактическому словарю. Поэтому, когда вы захотите написать в YourDictionary [Key1], вы сначала заблокируете InternalLocksDictionary [Key1], так что только один поток будет писать в YourDictionary.
(не слишком чистый) пример может быть найдено здесь.
Просто наткнулся на это и подумал, что я поделился каким-то кодом, который я написал несколько лет назад, где мне нужен был словарь на ключевой основе.
using (var lockObject = new Lock(hashedCacheID))
{
var lockedKey = lockObject.GetLock();
//now do something with the dictionary
}
класс блокировки
class Lock : IDisposable
{
private static readonly Dictionary<string, string> Lockedkeys = new Dictionary<string, string>();
private static readonly object CritialLock = new object();
private readonly string _key;
private bool _isLocked;
public Lock(string key)
{
_key = key;
lock (CritialLock)
{
//if the dictionary doesnt contain the key add it
if (!Lockedkeys.ContainsKey(key))
{
Lockedkeys.Add(key, String.Copy(key)); //enusre that the two objects have different references
}
}
}
public string GetLock()
{
var key = Lockedkeys[_key];
if (!_isLocked)
{
Monitor.Enter(key);
}
_isLocked = true;
return key;
}
public void Dispose()
{
var key = Lockedkeys[_key];
if (_isLocked)
{
Monitor.Exit(key);
}
_isLocked = false;
}
}
Не ответ, но вы знаете, что если у вас уже есть пара, вы можете использовать pair.Value вместо повторного вызова оператора [] в Словаре?