Должен ли оператор возврата находиться внутри или снаружи замка?

Я просто понял, что в каком-то месте в моем коде у меня есть оператор return внутри замка и когда-то снаружи. Какой из них лучше?

1)

void example()
{
    lock (mutex)
    {
    //...
    }
    return myData;
}

2)

void example()
{
    lock (mutex)
    {
    //...
    return myData;
    }

}

Какой я должен использовать?

Ответ 1

По сути, это делает код проще. Единственная точка выхода - хороший идеал, но я бы не сгибал код из формы, чтобы достичь этого... И если альтернатива объявляет локальную переменную (вне блокировки), инициализирует ее (внутри блокировки) и затем, возвращая его (вне замка), я бы сказал, что простой "возврат 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 является более простым/чистым битом С#)

И посмотрите на 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 для простоты, но я тоже не стал бы волноваться.

Ответ 2

Это не имеет никакого значения; они оба переводятся на одно и то же с помощью компилятора.

Чтобы уточнить, либо эффективно переведен на что-то со следующей семантикой:

T myData;
Monitor.Enter(mutex)
try
{
    myData= // something
}
finally
{
    Monitor.Exit(mutex);
}

return myData;

Ответ 3

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

Ответ 4

Если вы думаете, что внешний вид блокировки выглядит лучше, но будьте осторожны, если вы в конечном итоге измените код на:

return f(...)

Если f() нужно вызвать с блокировкой, то она, очевидно, должна находиться внутри блокировки, так как сохранение возвратов внутри блокировки для согласованности имеет смысл.

Ответ 5

Это зависит,

Я собираюсь пойти против зерна здесь. Я обычно возвращался внутрь замка.

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

Итак, ваше сравнение на самом деле ошибочно. В идеале разница между двумя вариантами будет такой, как вы писали, что, по-видимому, дает повод для случая 1, на практике это немного уродливее.

void example() { 
    int myData;
    lock (foo) { 
        myData = ...;
    }
    return myData
}

против.

void example() { 
    lock (foo) {
        return ...;
    }
}

Я считаю, что случай 2 значительно легче читать и сложнее ввернуть, особенно для коротких фрагментов.

Ответ 6

Чтобы разработчики могли читать код, я бы предложил первый вариант.

Ответ 7

Для чего стоит, документация на MSDN имеет пример возврата изнутри блокировки. Из других ответов здесь видно, что это довольно похоже на IL, но для меня кажется, что безопаснее возвращаться изнутри блокировки, потому что тогда вы не рискуете перезаписать переменную, заменяемую другим потоком.

Ответ 8

Внешний вид выглядит чище.