Должен ли оператор return быть внутри или снаружи замка?


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

1)

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

2)

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

}

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

9 120

9 ответов:

по существу, который-когда-либо делает код проще. Одна точка выхода-хороший идеал, но я бы не стал сгибать код из формы только для его достижения... И если альтернатива объявляет локальную переменную (вне замка), инициализируя ее (внутри замка), а затем возвращая ее (вне замка), то я бы сказал, что простой "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 CS00,
        [1] object CS01)
    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 CS00)
    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 для простоты, но я бы не стал волноваться ни о том, ни о другом.

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

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

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
}

и

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

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

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

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

снаружи выглядит чище.

lock() return <expression> заявления всегда:

1) введите lock

2) делает локальное (потокобезопасное) хранилище для значения указанного типа,

3) заполняет магазин значением, возвращенным <expression>,

4) выход блокировки

5) верните магазин.

это означает, что значение, возвращаемое из оператора lock, всегда "варится" перед возвратом.

не беспокойтесь о lock() return, не слушайте здесь никого))