Bir iade ifadesi bir kilidin içinde mi yoksa dışında mı olmalıdır?


142

Ben sadece benim kod bazı yerlerde kilit içinde ve bazen dışında iade ifadesi olduğunu fark ettim. Hangisi en iyisi?

1)

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

2)

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

}

Hangisini kullanmalıyım?


Reflektör ateşlemeye ve IL karşılaştırması yapmaya ne dersiniz ;-).
Pop Catalin

6
@Pop: bitti - IL açısından da daha iyi değil - sadece C # stili geçerlidir
Marc Gravell

1
Çok ilginç, wow bugün bir şeyler öğreniyorum!
Pokus

Yanıtlar:


192

Temel olarak, kod hiç bu kadar kolay olmamıştı. Tek çıkış noktası güzel bir idealdir, ancak kodu sadece elde etmek için şekilden bükmezdim ... Ve alternatif yerel bir değişken (kilit dışında) bildiriyorsa, (kilidin içinde) ve sonra geri (kilit dışında), sonra kilit içinde basit bir "dönüş foo" çok daha basit olduğunu söyleyebilirim.

IL'deki farkı göstermek için, şu kodu girelim:

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;
    }
}

(mutlu bir şekilde ReturnInsideC # daha basit / temiz bir bit olduğunu iddia ediyorum unutmayın )

Ve IL'ye bakın (serbest bırakma modu vb.):

.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
}

Yani IL seviyesinde [bazı isimler verin veya alın] aynıdır (bir şey öğrendim ;-p). Bu nedenle, tek mantıklı karşılaştırma, yerel kodlama stilinin (oldukça öznel) yasasıdır ... ReturnInsideBasitliği tercih ederim , ama ikisinden de heyecanlanmam.


15
(Ücretsiz ve mükemmel) Red Gate'in .NET Reflektörünü kullandım (öyleydi: Lutz Roeder'ın .NET Reflektörü), ancak ILDASM de yapardı.
Marc Gravell

1
Reflektörün en güçlü yönlerinden biri, IL'yi tercih ettiğiniz dile (C #, VB, Delphi, MC ++, Krom, vb.) Aslında parçalarına ayırabilmenizdir
Marc Gravell

3
Basit örneğiniz için IL aynı kalır, ancak bunun nedeni muhtemelen yalnızca sabit bir değer döndürmenizdir ?! Gerçek hayat senaryoları için sonucun farklı olabileceğine inanıyorum ve paralel iş parçacıkları, döndürülmeden önce değeri değiştirerek birbirleriyle sorunlara neden olabilir, dönüş ifadesi kilit bloğunun dışında. Tehlikeli!
Torbjørn

@MarcGravell: Aynı şeyi düşündüğünüzde yazınızla karşılaştım ve cevabınızı okuduktan sonra bile hala aşağıdakilerden emin değilim: Dış yaklaşımı kullanmanın iş parçacığı açısından güvenli mantığı bozabileceği HERHANGİ BİR şart var mı? Tek bir geri dönüş noktasını tercih ettiğimden ve iplik güvenliği hakkında iyi hissetmediğinizden bunu soruyorum. IL aynı ise, endişem zaten tartışmalı olmalıdır.
Raheel Khan

1
@RaheelKhan hayır, hiçbiri; onlar aynı. IL düzeyinde, bir bölge içinde olamazsınız . ret.try
Marc Gravell

42

Hiçbir fark yaratmaz; ikisi de derleyici tarafından aynı dile çevrilir.

Açıklığa kavuşturmak için, ya etkili bir şekilde aşağıdaki semantik ile bir şeye çevrilir:

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

return myData;

1
Peki, bu denemek / nihayet doğru - ancak, kilit dışında dönüş hala optimize edilemez ekstra yerli gerektirir - ve daha fazla kod alır ...
Marc Gravell

3
Bir deneme bloğundan geri dönemezsiniz; ".leave" op koduyla bitmelidir. Bu nedenle yayılan CIL her iki durumda da aynı olmalıdır.
Greg Beech

3
Haklısın - sadece IL'ye baktım (güncellenmiş gönderiye bakın). Bir şey öğrendim ;-p
Marc Gravell

2
Harika, maalesef ağrılı saatlerden deneme bloklarında .ret op kodları yayınlamaya çalıştığımı ve CLR'nin dinamik yöntemlerimi yüklemeyi reddettiğini öğrendim :-(
Greg Beech

Ben ilgili olabilir; Oldukça fazla Yansıma yaptım. bir şey hakkında çok emin değilseniz, ben C # temsili kod yazmak ve sonra IL bak. Ancak IL terimleriyle ne kadar hızlı düşünmeye başladığınız şaşırtıcıdır (yani yığını sıralamak).
Marc Gravell

28

Kesinlikle kilit içinde dönüş koymak istiyorum. Aksi takdirde, başka bir iş parçacığının kilide girip dönüş ifadesinden önce değişkeninizi değiştirmesi riskini alırsınız, bu nedenle orijinal arayanın beklenenden farklı bir değer almasını sağlarsınız.


4
Bu doğru, diğer yanıtlayıcıların eksik olduğu bir nokta. Yaptıkları basit örnekler aynı IL'yi üretebilir, ancak bu çoğu gerçek hayat senaryosu için geçerli değildir.
Torbjørn

4
Diğer cevaplar bu konuda konuşma dont şaşırttı
Akshat Agarwal

5
Bu örnekte, dönüş değerini saklamak için bir yığın değişkeni kullanmaktan, yani sadece kilit dışındaki dönüş deyiminden ve elbette değişken bildiriminden bahsediyorlar. Başka bir iş parçacığının başka bir yığını olmalı ve bu nedenle herhangi bir zarar veremez, değil mi?
Guillermo Ruffino

3
Başka bir iş parçacığı dönüş çağrısı ve ana iş parçacığındaki değişkene dönüş değeri gerçek ataması arasındaki değeri güncelleştirebilir, bu geçerli bir nokta olduğunu sanmıyorum. Döndürülen değer değiştirilemez veya mevcut gerçek değerle her iki şekilde tutarlılığı garanti edilemez. Sağ?
Uroš Joksimović

Bu cevap yanlış. Başka bir evre yerel bir değişkeni değiştiremez. Yerel değişkenler yığın içinde saklanır ve her iş parçacığının kendi yığını vardır. Ancak bir iş parçacığı yığınının varsayılan boyutu 1 MB'dir .
Theodor Zoulias

5

Değişir,

Buradaki tahıllara karşı gideceğim. Genellikle kilidin içine dönecekti.

Genellikle değişken verilerim yerel bir değişkendir. Yerel değişkenleri başlatırken bildirmeyi çok seviyorum. Nadiren kilitlerimin dışında dönüş değerimi başlatmak için verilerim var.

Yani karşılaştırmanız aslında hatalı. İdeal olarak, iki seçenek arasındaki fark, yazdığınız gibi olacaktır, ki bu da durum 1'e başını veriyor gibi görünüyor, pratikte biraz daha çirkin.

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

vs.

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

Özellikle kısa parçacıklar için 2. durumun okunması çok daha kolay ve daha zor vidalanması olduğunu düşünüyorum.


4

Dışarıdaki kilidin daha iyi göründüğünü düşünüyorsanız, ancak kodu şu şekilde değiştirirseniz dikkatli olun:

return f(...)

Eğer f () kilit basılıyken çağrılmaya ihtiyaç duyulursa, açık bir şekilde kilidin içinde olması gerekir;


1

Değeri ne olursa olsun , MSDN'deki belgelerin kilidin içinden dönme örneği var. Buradaki diğer cevaplardan, oldukça benzer IL gibi görünüyor, ancak bana göre, kilidin içinden geri dönmek daha güvenli görünüyor, çünkü o zaman başka bir iş parçacığı tarafından bir dönüş değişkeninin üzerine yazılma riskini çalıştırmıyorsunuz.


0

Diğer geliştiricilerin kodu okumasını kolaylaştırmak için ilk alternatifi öneririm.


0

lock() return <expression> ifadeler her zaman:

1) kilidi girin

2) belirtilen türün değeri için yerel (iş parçacığı açısından güvenli) depo yapar,

3) mağazayı döndürdüğü değerle doldurur <expression>,

4) çıkış kilidi

5) mağaza iade.

Bu, kilit ifadesinden döndürülen ve dönüşten önce her zaman "pişirilen" değerin anlamına gelir.

Endişelenme lock() return, burada kimseyi dinleme))


-2

Not: Bu cevabın gerçekten doğru olduğuna inanıyorum ve umarım bu da yardımcı olur, ancak her zaman somut geri bildirimlere dayanarak geliştirmekten memnuniyet duyarım.

Mevcut cevapları özetlemek ve tamamlamak için:

  • Kabul cevap bakılmaksızın hangi sözdizimi biçimi size seçtiğiniz, gösteriler C # zamanında ve dolayısıyla - - IL kodunda, kod returnkadar olmaz sonra kilit serbest bırakılır.

    • Hatta yerleştirilmesi rağmen return lock saptıran, kontrol akışını kesinlikle, blok nedenle, bu yöntem : [1] , bu sözdizimsel olan uygun bir aux dönüş değeri saklamak için ihtiyacı ortadan kaldırmaktadır olmasıyla. yerel değişken (blok dışında bildirilir, böylece blok dışında kullanılabilir return) - Edward KMETT'nin cevabına bakınız .
  • Ayrı olarak - ve bu yön soruya rastlantısaldır , ancak yine de ilgi çekici olabilir ( Ricardo Villamil'in cevabı buna hitap etmeye çalışır, ancak yanlış, sanırım) - bir lockifadeyi bir ifadeyle birleştirmek return- yani, returnkorunan bir blokta değer elde etmek eşzamanlı erişim - yalnızca bir kez elde edildikten sonra korumaya ihtiyaç duymadığında , aşağıdaki senaryolarda geçerli olan , arayanın kapsamındaki döndürülen değeri anlamlı bir şekilde "korur" :

    • Döndürülen değer, bir öğeden yalnızca öğe ekleme ve kaldırma açısından korunmaya ihtiyaç duyan bir öğeyse, öğelerin kendilerinin modifikasyonları ve / veya ...

    • ... döndürülen değer bir değer türünün veya dizenin örneğiyse .

      • Bu durumda, arayanın değerin bir anlık görüntüsünü (kopyasını) [2] aldığını unutmayın; bu durum, arayanı incelerken artık kaynak veri yapısındaki geçerli değer olmayabilir.
    • Başka bir durumda, kilitleme , yöntemin içinde (sadece) değil , arayan tarafından yapılmalıdır .


[1] Theodor Zoulias bu teknik, aynı zamanda doğru yerleştirmek için olduğuna işaret returniçindeki try, catch, using, if, while, for, ... ifadeleri; bununla birlikte, lockbu sorunun sorulduğu ve çok ilgi gördüğü gibi, gerçek kontrol akışının incelenmesini davet etmesi muhtemel olan ifadenin özel amacıdır .

[2] Değer türü bir örneğe erişmek, her zaman yığının yerel, yığın halinde bir kopyasını oluşturur; dizeler teknik olarak başvuru türü örnekler olsa da, etkin şekilde benzer değer türü örnekler olarak davranırlar.


Cevabınızın şu anki durumu (revizyon 13) ile ilgili olarak, hala lockdönüş ifadesinin varlığının nedenleri hakkında spekülasyonlar yapıyorsunuz ve dönüş ifadesinin yerleştirilmesinden anlam çıkarıyorsunuz. IMHO'nun bu soruyla ilgisi olmayan bir tartışma. Ayrıca "yanlış beyanların" kullanımını oldukça rahatsız edici buluyorum . Bir dönen ise locksaptıran kontrol akışını, o zaman aynı olan bir dönüş için söz konusu olabilir try, catch, using, if, while, for, ve dilin diğer herhangi bir yapı. Bu C # kontrol akış yanlış beyanları ile bilmece olduğunu söylemek gibi. İsa ...
Theodor Zoulias

"Bu C # kontrol akış yanlış beyanları ile bilmece olduğunu söylemek gibi" - Bu teknik olarak doğru ve "yanlış temsil" terimi sadece bu şekilde almayı seçerseniz bir değer yargısıdır. İle try, if... ... Ben şahsen bunu düşünmeye bile meyilli değilim, ama locközellikle, benim için ortaya çıkan soru bağlamında - ve başkaları için de ortaya çıkmazsa, bu soru asla sorulmazdı ve kabul edilen cevap, gerçek davranışı araştırmak için çok uzun sürmezdi.
mklement0
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.