Правка для ознакомления
Мы знаем, что параметр ref в C# передает ссылку на переменную, позволяя изменять саму внешнюю переменную в вызываемом методе. Но обрабатывается ли ссылка так же, как указатель C (чтение текущего содержимого исходной переменной при каждом доступе к этому параметру и изменение исходной переменной при каждом изменении параметра), или может ли вызываемый метод полагаться на непротиворечивую ссылку для продолжительность разговора? Первый вызывает некоторые проблемы с безопасностью потоков. Особенно:
Я написал статический метод на С#, который передает объект по ссылке:
public static void Register(ref Definition newDefinition) { ... }
Вызывающий объект предоставляет завершенный, но еще не зарегистрированный объект Definition
, и после некоторой проверки согласованности мы «регистрируем» предоставленное им определение. Однако, если уже есть определение с тем же ключом, он не может зарегистрировать новое, и вместо этого их ссылка обновляется до «официальной» Definition
для этого ключа.
Мы хотим, чтобы это было строго потокобезопасным, но на ум приходит патологический сценарий. Предположим, что клиент (использующий нашу библиотеку) делится ссылкой не потокобезопасным способом, например, используя статический член, а не локальную переменную:
private static Definition riskyReference = null;
Если один поток устанавливает riskyReference = new Definition("key 1");
, заполняет определение и вызывает наш Definition.Register(ref riskyReference);
, в то время как другой поток также решает установить riskyReference = new Definition("key 2");
, гарантируем ли мы, что в нашем методе Register обрабатываемая нами ссылка newDefinition
не будет изменена другими потоками (поскольку ссылка на объект была скопирована и будет скопирована, когда мы вернемся?), или этот другой поток может заменить объект на нас в середине нашего выполнения (если мы ссылаемся на указатель на исходное место хранения?? ?) и тем самым нарушить нашу проверку на вменяемость?
Обратите внимание, что это отличается от изменений в самом базовом объекте, которые, конечно, возможны для ссылочного типа (класса), но от которых можно легко защититься соответствующей блокировкой внутри этого класса. Однако мы не можем защитить изменения самого пространства переменных внешнего клиента! Нам пришлось бы сделать свою собственную копию параметра в верхней части метода и перезаписать параметр в нижней части (например), но компилятору кажется, что это имеет больше смысла делать за нас, учитывая безумие обработки небезопасная ссылка.
Итак, я склонен думать, что ссылка может быть скопирована и скопирована компилятором, так что метод обрабатывает непротиворечивую ссылку на исходный объект (пока он не изменит свою собственную ссылку, когда захочет) независимо от того, что может происходит с исходным местоположением в других потоках. Но у нас возникли проблемы с поиском окончательного ответа на этот вопрос в документации и обсуждении параметров ссылки.
Может ли кто-нибудь успокоить мою озабоченность окончательной цитатой?
Редактировать для заключения:
Подтвердив это на примере многопоточного кода (спасибо, Марк!) и подумав об этом, становится понятно, что это действительно не-автоматически- поведение threadsafe, о котором я беспокоился. Одним из пунктов «ref» является передача больших структур по ссылке, а не их копирование. Другая причина заключается в том, что вы можете захотеть настроить долгосрочный мониторинг переменной и передать ссылку на нее, которая будет видеть изменения в переменной (например, изменение между нулевым и активным объектом ), что невозможно при автоматическом копировании.
Итак, чтобы сделать наш метод Register
устойчивым к безумию клиента, мы могли бы реализовать его следующим образом:
public static void Register(ref Definition newDefinition) {
Definition theDefinition = newDefinition; // Copy in.
//... Sanity checks, actual work...
//...possibly changing theDefinition to a new Definition instance...
newDefinition = theDefinition; // Copy out.
}
У них по-прежнему будут свои проблемы с потоками в том, что они в конечном итоге получат, но, по крайней мере, их безумие не нарушит наш собственный процесс проверки работоспособности и, возможно, пропустит плохое состояние мимо наших проверок.