Редактировать для вступления:
Мы знаем, что параметр ref в C # передает ссылку в переменную, что позволяет изменять внешнюю переменную внутри вызываемого метода. Но обрабатывается ли ссылка так же, как указатель C (чтение текущего содержимого исходной переменной при каждом доступе к этому параметру и изменение исходной переменной при каждой модификации параметра), или вызываемый метод может полагаться на непротиворечивую ссылку для длительность звонка? Первый поднимает некоторые проблемы безопасности потоков. В частности:
Я написал статический метод в C #, который передает объект по ссылке:
public static void Register(ref Definition newDefinition) { ... }
Вызывающий объект предоставляет завершенный, но еще не зарегистрированный объект Definition
, и после некоторой проверки согласованности мы «регистрируем» предоставленное им определение. Однако, если уже существует определение с тем же ключом, оно не может зарегистрировать новое, и вместо этого их ссылка обновляется до «официального» * 1012 * для этого ключа.
Мы хотим, чтобы это было строго поточно-ориентированным, но на ум приходит патологический сценарий. Предположим, что клиент (использующий нашу библиотеку) делится ссылкой не поточно-ориентированным образом, например, используя статический член, а не локальную переменную:
private static Definition riskyReference = null;
Если один поток устанавливает riskyReference = new Definition("key 1");
, заполняет определение и вызывает наш Definition.Register(ref riskyReference);
, в то время как другой поток также решает установить riskyReference = new Definition("key 2");
, мы гарантируем, что в нашем методе Register ссылка newDefinition
, которую мы обрабатываем не будут изменены на нас другими потоками (потому что ссылка на объект была скопирована и будет скопирована, когда мы вернемся?), или этот другой поток может заменить объект на нас в середине нашего выполнения (если мы ' ссылаетесь на указатель на исходное место хранения ???) и таким образом нарушаете нашу проверку работоспособности?
Обратите внимание, что это отличается от изменений самого базового объекта, которые, конечно, возможны для ссылочного типа (класса), но могут быть легко защищены с помощью соответствующей блокировки в этом классе. Однако мы не можем охранять изменения самого пространства переменных внешнего клиента! Мы должны были бы сделать нашу собственную копию параметра в верхней части метода и перезаписать параметр в нижней части (например), но для компилятора это будет иметь больше смысла для нас, учитывая безумие обработки небезопасная ссылка.
Итак, я склонен думать, что компилятор может скопировать и скопировать ссылку, чтобы метод обрабатывал непротиворечивую ссылку на исходный объект (до тех пор, пока не изменит свою собственную ссылку, когда захочет), независимо от того, о том, что может происходить с исходным местоположением в других потоках. Но у нас возникли проблемы с поиском окончательного ответа по этому вопросу при документировании и обсуждении параметров ссылок.
Может кто-нибудь утешить мою озабоченность окончательным цитированием?
Редактировать для вывода:
Подтвердив это на примере многопоточного кода (спасибо Марку!) И подумав об этом, становится понятным, что это действительно поведение не автоматически-поточно-безопасное , о котором я беспокоился. Одна из точек «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.
}
У них все еще были бы свои собственные проблемы с потоками в том, что они в итоге получили, но по крайней мере их безумие не нарушило бы наш собственный процесс проверки работоспособности и, возможно, ускользнуло бы из-за наших проверок.