Я только что понял, что в каком-то месте моего кода оператор return находится внутри замка, а иногда и снаружи. Какой из них лучше?
1)
void example()
{
lock (mutex)
{
//...
}
return myData;
}
2)
void example()
{
lock (mutex)
{
//...
return myData;
}
}
Какой мне использовать?
c#
.net
multithreading
mutex
Патрик Дежарден
источник
источник
Ответы:
По сути, что-то делает код проще. Единая точка выхода - хороший идеал, но я бы не стал искажать код только для того, чтобы добиться этого ... И если альтернативой является объявление локальной переменной (вне блокировки), ее инициализация (внутри блокировки) и затем вернуть его (вне блокировки), тогда я бы сказал, что простой "return foo" внутри блокировки намного проще.
Чтобы показать разницу в IL, давайте закодируем:
static class Program { static void Main() { } static readonly object sync = new object(); static int GetValue() { return 5; } static int ReturnInside() { lock (sync) { return GetValue(); } } static int ReturnOutside() { int val; lock (sync) { val = GetValue(); } return val; } }
(обратите внимание, что я с удовольствием утверждаю, что
ReturnInside
это более простой / чистый бит C #)И посмотрите на IL (режим выпуска и т. Д.):
.method private hidebysig static int32 ReturnInside() cil managed { .maxstack 2 .locals init ( [0] int32 CS$1$0000, [1] object CS$2$0001) L_0000: ldsfld object Program::sync L_0005: dup L_0006: stloc.1 L_0007: call void [mscorlib]System.Threading.Monitor::Enter(object) L_000c: call int32 Program::GetValue() L_0011: stloc.0 L_0012: leave.s L_001b L_0014: ldloc.1 L_0015: call void [mscorlib]System.Threading.Monitor::Exit(object) L_001a: endfinally L_001b: ldloc.0 L_001c: ret .try L_000c to L_0014 finally handler L_0014 to L_001b } method private hidebysig static int32 ReturnOutside() cil managed { .maxstack 2 .locals init ( [0] int32 val, [1] object CS$2$0000) L_0000: ldsfld object Program::sync L_0005: dup L_0006: stloc.1 L_0007: call void [mscorlib]System.Threading.Monitor::Enter(object) L_000c: call int32 Program::GetValue() L_0011: stloc.0 L_0012: leave.s L_001b L_0014: ldloc.1 L_0015: call void [mscorlib]System.Threading.Monitor::Exit(object) L_001a: endfinally L_001b: ldloc.0 L_001c: ret .try L_000c to L_0014 finally handler L_0014 to L_001b }
Так что на уровне IL они [дайте-ка некоторые имена] идентичны (я кое-что узнал ;-p). Таким образом, единственное разумное сравнение - это (в высшей степени субъективный) закон локального стиля кодирования ... Я предпочитаю
ReturnInside
простоту, но я бы тоже не волновался.источник
ret
внутри.try
региона.Это не имеет значения; они оба переведены компилятором в одно и то же.
Чтобы уточнить, либо эффективно переводится во что-то со следующей семантикой:
T myData; Monitor.Enter(mutex) try { myData= // something } finally { Monitor.Exit(mutex); } return myData;
источник
Я бы обязательно вложил возврат в замок. В противном случае вы рискуете, что другой поток войдет в блокировку и изменит вашу переменную перед оператором return, поэтому исходный вызывающий объект получит другое значение, чем ожидалось.
источник
Если вы думаете, что замок снаружи выглядит лучше, но будьте осторожны, если вы в конечном итоге измените код на:
return f(...)
Если f () необходимо вызвать с удерживаемой блокировкой, то очевидно, что она должна находиться внутри блокировки, так как сохранение возвратов внутри блокировки для согласованности имеет смысл.
источник
Это зависит,
Я собираюсь пойти против течения здесь. Я бы вообще вернулся внутрь замка.
Обычно переменная mydata является локальной переменной. Я люблю объявлять локальные переменные при их инициализации. У меня редко есть данные для инициализации возвращаемого значения за пределами моей блокировки.
Так что ваше сравнение на самом деле ошибочно. Хотя в идеале разница между двумя вариантами будет такой, как вы написали, что, кажется, отдает предпочтение случаю 1, на практике это немного уродливее.
void example() { int myData; lock (foo) { myData = ...; } return myData }
vs.
void example() { lock (foo) { return ...; } }
Я считаю, что случай 2 намного легче читать и труднее ошибиться, особенно для коротких фрагментов.
источник
Как бы то ни было, в документации MSDN есть пример возврата изнутри блокировки. Из других ответов здесь, похоже, он очень похож на IL, но мне кажется безопаснее вернуться изнутри блокировки, потому что тогда вы не рискуете перезаписать возвращаемую переменную другим потоком.
источник
Чтобы другим разработчикам было легче читать код, я бы предложил первую альтернативу.
источник
lock() return <expression>
заявления всегда:1) введите блокировку
2) делает локальное (поточно-ориентированное) хранилище для значения указанного типа,
3) заполняет магазин значением, возвращаемым
<expression>
,4) блокировка выхода
5) вернуть в магазин.
Это означает, что значение, возвращаемое оператором блокировки, всегда «готовится» перед возвратом.
Не переживайте
lock() return
, здесь никого не слушайте))источник
Примечание: я считаю, что этот ответ является фактически правильным, и я надеюсь, что он тоже полезен, но я всегда рад улучшить его на основе конкретных отзывов.
Подведем итог и дополним существующие ответы:
В ответ принятые показывает , что, независимо от того, какой синтаксис формы вы выбираете в вашей C # код, в код IL - и , следовательно , во время выполнения -
return
не произойдет до тех пор , после того, как блокировка будет снята.return
внутриlock
блок поэтому, строго говоря, искажает поток управления [1] , то синтаксический удобно тем , что отпадает необходимость в хранении возвращаемого значения в AUX. локальная переменная (объявленная вне блока, чтобы ее можно было использовать сreturn
вне блока) - см . ответ Эдварда КМЕТТА .Отдельно - и этот аспект является второстепенным для вопроса, но все же может представлять интерес ( ответ Рикардо Вильямиля пытается решить его, но, как мне кажется, неверно) - объединение
lock
оператора сreturn
оператором, то есть получение значенияreturn
в блоке, защищенном от одновременный доступ - значимо «защищает» возвращаемое значение в области действия вызывающего , только если оно действительно не нуждается в защите после получения , что применяется в следующих сценариях:Если возвращаемое значение является элементом из коллекции, который нуждается в защите только с точки зрения добавления и удаления элементов, а не с точки зрения модификации самих элементов и / или ...
... если возвращаемое значение является экземпляром типа значения или строкой .
В любом другом случае блокировка должна выполняться вызывающим , а не (только) внутри метода.
[1] Theodor Zoulias указывает на то , что это технически верно и для размещения
return
внутриtry
,catch
,using
,if
,while
,for
, ... заявления; однако именно конкретная цельlock
утверждения, вероятно, потребует тщательного изучения истинного потока управления, о чем свидетельствует вопрос, который был задан и получил много внимания.[2] Доступ к экземпляру типа значения неизменно создает его локальную для потока копию в стеке; даже несмотря на то, что строки технически являются экземплярами ссылочного типа, они эффективно ведут себя экземплярами типа аналогичного значения.
источник
lock
оператора return и извлекаете смысл из его размещения. Это обсуждение, не связанное с этим вопросом, ИМХО. Также меня беспокоит использование словосочетания «искажение фактов» . Если возвращения изlock
искажает поток управления, то же самое можно сказать и о возвращении изtry
,catch
,using
,if
,while
,for
, и любая другая конструкция языка. Это все равно что сказать, что C # пронизан искажениями потока управления. Иисус ...try
,if
... лично я не склонен даже думать об этом, но и в контекстеlock
, в частности, возник вопрос для меня - и , если он не возникал для других тоже, этот вопрос никогда бы не спрашивали , и принятый ответ не пошел бы на изучение истинного поведения.