2009-02-04 14 views
73

Aşağıdaki iki snippet'te, birincisi güvenli mi yoksa ikincisini mi yapmalısınız?Foreach tanımlayıcısı ve kapatır

Güvenli bir şekilde, her bir iş parçacığının, iş parçacığının oluşturulduğu aynı döngü yinelemesinden Foo'daki yöntemi çağırması garanti edilir.

Veya referansı, döngüdeki her yineleme için "yerel" yeni bir değişkene kopyalamanız gerekir mi?

var threads = new List<Thread>(); 
foreach (Foo f in ListOfFoo) 
{  
    Thread thread = new Thread(() => f.DoSomething()); 
    threads.Add(thread); 
    thread.Start(); 
} 

-

var threads = new List<Thread>(); 
foreach (Foo f in ListOfFoo) 
{  
    Foo f2 = f; 
    Thread thread = new Thread(() => f2.DoSomething()); 
    threads.Add(thread); 
    thread.Start(); 
} 

Güncelleme: olarak Jon Skeet yanıtında işaret, bu özel parçacığı ile ilgisi yoktur. aynı referansa

+0

Aslında iş parçacığı kullanmıyormuşsunuz gibi düşünmek zorunda olduğumu hissediyorum, doğru delege çağırırsınız. Jon Skeet'in iş parçacığı olmadan yaptığı örnekte sorun, 2 döngü olduğu. Burada sadece bir tane var, bu yüzden bir sorun olmamalı ... kodun ne zaman işleyeceğini tam olarak bilmedikçe (iş parçacığı kullanırsanız - Marc Gravell'in cevabı mükemmel olduğunu gösterir). – user276648

+0

[Değiştirilmiş Kapanış Erişim (2)] 'nin olası kopyası (http://stackoverflow.com/questions/304258/access-to-modified-closure-2) – nawfal

+0

@ user276648 İş parçacığı gerektirmez. Döngülerin yürütülmesini ertelemek, bu davranışı elde etmek için yeterli olacaktır. – binki

cevap

97

Düzenleme: değişkenin tanımlandığı yerde (derleyicinin gözünde) bir değişiklikle bu C# 5'deki tüm değişiklikler. C# 5'ten itibaren, onlar aynı.


İkincisi güvenlidir; ilk değil. yani

Foo f; 
while(iterator.MoveNext()) 
{ 
    f = iterator.Current; 
    // do something with f 
} 

Bu orada sadece 1 f kapatma kapsamı bakımından, ve ipler çok büyük olasılıkla karışık olabileceği anlamına gelir - - foreach ile

, değişken dışında döngü ilan edilir çağıran yöntem bazı durumlarda birden çok kez ve başkalarında hiç yoktur. Sen içinde ikinci değişken bildiriminde ile döngü bu düzeltebilirsiniz:

foreach(Foo f in ...) { 
    Foo tmp = f; 
    // do something with tmp 
} 

Bu daha sonra her kapatma kapsamında ayrı tmp vardır, bu yüzden bu konuda hiçbir riski yoktur.

static void Main() 
    { 
     int[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 
     foreach (int i in data) 
     { 
      new Thread(() => Console.WriteLine(i)).Start(); 
     } 
     Console.ReadLine(); 
    } 

Çıkışlar (rastgele): Burada

sorunun basit bir kanıtı

1 
3 
4 
4 
5 
7 
7 
8 
9 
9 

geçici değişkeni ekleyin ve çalışır:

 foreach (int i in data) 
     { 
      int j = i; 
      new Thread(() => Console.WriteLine(j)).Start(); 
     } 

(her numara bir kez, ancak tabi ki sipariş garanti edilmez)

+0

Teşekkür ederim, Marc. Güzel mesaj. – xyz

+0

Kutsal inek ... o eski yazı bana bir sürü baş ağrısını kurtardı. Her zaman foreach değişkeninin döngü içinde kapsamayı bekledim. Bu önemli bir WTF tecrübesiydi. – chris

+0

Aslında bu, foreach döngüsünde bir hata olarak kabul edildi ve derleyicide sabitlendi. (Değişkenin tüm döngü için tek örneğinin olduğu döngüden farklı olarak.) –

-5
Foo f2 = f; 

puan

f 

Yani hiçbir şey kaybetmiş ve hiçbir şey elde ...

+0

Buradaki nokta, derleyicinin yerel değişkeni örtük olarak oluşturulan kapanışın kapsamına kaldırmak için burada bir sihir gerçekleştirmesidir. Soru, derleyicinin döngü değişkeni için bunu yapması gerektiğini anlamasıdır. Derleyici kaldırma ile ilgili birkaç zayıflıktan dolayı biliniyor, bu yüzden bu iyi bir soru. –

+1

Bu sihir değil. Sadece çevreyi yakalar. Burada ve döngüler için problem, yakalama değişkeninin mutasyona uğradığı (yeniden atanmış) olmasıdır. – leppie

+1

leppie: derleyici sizin için kod oluşturur ve genel olarak görmek kolay değildir * ne * bu tam olarak kod. Bu, eğer * varsa, derleyici sihrinin * tanımıdır. –

16

seçenek 2 kullanmak Senin ihtiyacını, kullanacağı değişen değişken etrafında bir kapatma oluşturmak Değişken kullanıldığında ve kapanma oluşturma süresinde değilken değişkenin değeri. The implementation of anonymous methods in C# and its consequences (part 2)

The implementation of anonymous methods in C# and its consequences (part 3)

Düzenleme

The implementation of anonymous methods in C# and its consequences (part 1): bir değişkenin değerini ancak değişken kendisini yakalamak yok anlamına gelen "sözcük kapanışları" dir C# kapanışları içinde, net olarak iletmek. Bu, değişen bir değişkenin kapanmasını yaratırken, kapağın aslında, değişkenin bir kopyasının değil, değişkenin bir referansı olduğu anlamına gelir.

Düzenleme2: herhangi bir kişi derleyici içselleri hakkında okumakla ilgileniyorsa, tüm blog yazılarına bağlantılar ekledi.

+0

Bence bu değer ve referans türleri için geçerli. – leppie

33

Pop Catalin ve Marc Gravell'in cevapları doğru. Tek eklemek istediğim, my article about closures (hem Java hem de C# hakkında konuşur) bir bağlantıdır. Sadece biraz değer katabileceğini düşündüm.

DÜZENLEME: Bence iş parçacığının öngörülemezliğine sahip olmayan bir örnek vermeye değer. İşte her iki yaklaşımı gösteren kısa ama eksiksiz bir program. "Kötü eylem" listesi 10 defa basıyor; "Iyi eylem" liste 0'dan 9.

using System; 
using System.Collections.Generic; 

class Test 
{ 
    static void Main() 
    { 
     List<Action> badActions = new List<Action>(); 
     List<Action> goodActions = new List<Action>(); 
     for (int i=0; i < 10; i++) 
     { 
      int copy = i; 
      badActions.Add(() => Console.WriteLine(i)); 
      goodActions.Add(() => Console.WriteLine(copy)); 
     } 
     Console.WriteLine("Bad actions:"); 
     foreach (Action action in badActions) 
     { 
      action(); 
     } 
     Console.WriteLine("Good actions:"); 
     foreach (Action action in goodActions) 
     { 
      action(); 
     } 
    } 
} 
+1

Teşekkürler - Ben gerçekten konu hakkında olmadığını söylemek için soruyu ekledim. – xyz

+0

Aynı zamanda sitenizde görüntülü görüşmelerde de bulundu. Http://csharpindepth.com/Talks.aspx – rizzle

+0

Evet, hatırlattığım bir iş parçacığı sürümü kullandım ve geri bildirim önerilerinden biri de İpliklerden kaçınmak - yukarıdaki gibi bir örnek kullanmak daha nettir. –

3

Bu ilginç bir sorudur ve biz insanlar tüm çeşitli şekillerde cevap gördük gibi görünüyor için sayar. İkinci yolun tek güvenli yol olduğu izlenimi altındaydım. Eğer bu seçeneği 1 kesinlikle güvenli değildir göreceksiniz çalıştırırsanız

class Foo 
{ 
    private int _id; 
    public Foo(int id) 
    { 
     _id = id; 
    } 
    public void DoSomething() 
    { 
     Console.WriteLine(string.Format("Thread: {0} Id: {1}", Thread.CurrentThread.ManagedThreadId, this._id)); 
    } 
} 
class Program 
{ 
    static void Main(string[] args) 
    { 
     var ListOfFoo = new List<Foo>(); 
     ListOfFoo.Add(new Foo(1)); 
     ListOfFoo.Add(new Foo(2)); 
     ListOfFoo.Add(new Foo(3)); 
     ListOfFoo.Add(new Foo(4)); 


     var threads = new List<Thread>(); 
     foreach (Foo f in ListOfFoo) 
     { 
      Thread thread = new Thread(() => f.DoSomething()); 
      threads.Add(thread); 
      thread.Start(); 
     } 
    } 
} 

: Ben gerçek bir hızlı kanıtı şanti. Senin durumunda

+0

Tam program için teşekkürler :) – xyz

1

, sen eşleyerek kopyalama hile kullanmadan sorunu önlemek edebilirsiniz ListOfFoo parçacığı dizisine:

var threads = ListOfFoo.Select(foo => new Thread(() => foo.DoSomething())); 
foreach (var t in threads) 
{ 
    t.Start(); 
}