2009-02-04 11 views
73

दो निम्न स्निपेट में, पहला सुरक्षित है या आपको दूसरा करना चाहिए?फोरैच पहचानकर्ता और बंद

सुरक्षित से मेरा मतलब है कि प्रत्येक थ्रेड को उसी लूप पुनरावृत्ति से Foo पर विधि को कॉल करने की गारंटी है जिसमें थ्रेड बनाया गया था?

या क्या आप लूप के प्रत्येक पुनरावृत्ति के लिए संदर्भ को एक नए चर "स्थानीय" में प्रतिलिपि बनाना चाहिए?

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

अद्यतन: के रूप में जॉन स्कीट के जवाब में बताया, यह कुछ भी विशेष रूप से सूत्रण से कोई लेना देना नहीं है। के रूप में ही संदर्भ के लिए

+0

असल में मुझे लगता है कि इसे थ्रेडिंग के साथ करना है जैसे कि आप थ्रेडिंग का उपयोग नहीं कर रहे थे, तो आप सही प्रतिनिधि को कॉल करेंगे। थ्रेडिंग के बिना जॉन स्कीट के नमूने में, समस्या यह है कि 2 लूप हैं। यहां केवल एक ही है, इसलिए कोई समस्या नहीं होनी चाहिए ... जब तक आपको पता नहीं चलेगा कि कोड कब निष्पादित किया जाएगा (जिसका अर्थ है कि यदि आप थ्रेडिंग का उपयोग करते हैं - मार्क ग्रेवेल का जवाब पूरी तरह से दिखाता है)। – user276648

+0

संभावित डुप्लिकेट [संशोधित बंद करने के लिए एक्सेस (2)] (http://stackoverflow.com/questions/304258/access-to-modified-closure-2) – nawfal

+0

@ user276648 इसे थ्रेडिंग की आवश्यकता नहीं है। इस व्यवहार को पाने के लिए लूप पर्याप्त होने के बाद तक प्रतिनिधियों के निष्पादन को रोकें। – binki

उत्तर

97

संपादित करें: यह परिवर्तन सी # 5 में बदल गया है, जहां परिवर्तनीय परिभाषित किया गया है (कंपाइलर की आंखों में)। सी # 5 से आगे, वे वही हैं।


दूसरा सुरक्षित है; पहला नहीं है।

foreach साथ

, चर बाहर घोषित किया जाता है पाश - यानी

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

इसका मतलब यह है केवल 1 f बंद गुंजाइश के संदर्भ में है कि, और धागे बहुत संभावना भ्रमित हो सकता है - बुला कुछ मामलों पर विधि कई बार और दूसरों पर बिल्कुल नहीं। आप पाश अंदर एक दूसरे चर घोषणा साथ इसे ठीक कर सकते हैं:

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

यह फिर एक अलग tmp प्रत्येक बंद दायरे में है, इसलिए इस समस्या का कोई खतरा नहीं है।

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

आउटपुट (यादृच्छिक पर):

यहाँ समस्या का एक सरल सबूत है

1 
3 
4 
4 
5 
7 
7 
8 
9 
9 

एक अस्थायी चर जोड़ें और यह काम करता है:

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

(प्रत्येक संख्या एक बार, लेकिन निश्चित रूप से आदेश की गारंटी नहीं है)

+0

धन्यवाद, मार्क। अच्छा लेख। – xyz

+0

पवित्र गाय ... उस पुरानी पोस्ट ने मुझे बहुत सी सिरदर्द बचाया। मुझे हमेशा लूप के अंदर फोरैच वैरिएबल को स्कॉप्ड करने की उम्मीद थी। वह एक प्रमुख डब्ल्यूटीएफ अनुभव था। – chris

+0

वास्तव में फ़ोरैच-लूप में एक बग माना जाता था और संकलक में तय किया गया था। (फॉर-लूप के विपरीत जहां चर के पूरे लूप के लिए एकल उदाहरण है।) –

-5
Foo f2 = f; 

अंक

f 

तो कुछ भी नहीं खो दिया है और कुछ भी नहीं प्राप्त की ...

+0

बिंदु यह है कि संकलक स्थानीय चर को अंतर्निहित रूप से बनाए गए बंद होने के दायरे में उठाने के लिए यहां कुछ जादू करता है। सवाल यह है कि यदि संकलक लूप वैरिएबल के लिए ऐसा करने के लिए समझता है। संकलक उठाने के संबंध में कुछ कमजोरियों के लिए जाना जाता है, इसलिए यह एक अच्छा सवाल है। –

+1

यह जादू नहीं है। यह बस पर्यावरण को पकड़ता है। यहाँ और लूप के लिए समस्या यह है कि कैप्चर वैरिएबल उत्परिवर्तित हो जाता है (पुनः निर्दिष्ट)। – leppie

+1

leppie: कंपाइलर आपके लिए कोड उत्पन्न करता है, और सामान्य में यह देखना आसान नहीं है * क्या * कोड यह बिल्कुल है। यह * * * कंपाइलर जादू की परिभाषा है यदि कभी एक था। –

16

बदलते चर के चारों ओर एक बंद बनाने का उपयोग करेगा विकल्प 2 उपयोग करने के लिए आपका जरूरत, वैरिएबल का मान जब चर का उपयोग किया जाता है और बंद करने के समय पर नहीं।

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

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

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

संपादित करें: यह स्पष्ट सी # बंद में, बनाने के लिए कर रहे हैं "शाब्दिक बंद" अर्थ है कि वे एक वेरिएबल का मान लेकिन चर खुद पर कब्जा नहीं है। इसका मतलब है कि जब एक बदलते चर के लिए बंद करना बंद हो जाता है तो बंदरगाह वास्तव में परिवर्तनीय का संदर्भ है, इसकी कीमत की प्रति नहीं।

संपादित 2: सभी ब्लॉग पोस्ट में जोड़े गए लिंक अगर कोई संकलक आंतरिक के बारे में पढ़ने में रूचि रखता है।

+0

मुझे लगता है कि मूल्य और संदर्भ प्रकार के लिए जाता है। – leppie

33

पॉप कैटलिन और मार्क ग्रेवेल के जवाब सही हैं। मैं जो जोड़ना चाहता हूं वह my article about closures (जो जावा और सी # दोनों के बारे में बात करता है) का लिंक है। बस सोचा कि यह थोड़ा सा मूल्य जोड़ सकता है।

संपादित करें: मुझे लगता है कि यह एक उदाहरण देने लायक है जिसमें थ्रेडिंग की अप्रत्याशितता नहीं है। यहां एक छोटा लेकिन पूरा कार्यक्रम है जो दोनों दृष्टिकोण दिखा रहा है। "खराब कार्रवाई" सूची 10 दस बार प्रिंट करती है; "अच्छा कार्रवाई" सूची 0 से 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

धन्यवाद - मैंने यह प्रश्न जोड़ने के लिए कहा कि यह वास्तव में धागे के बारे में नहीं है। – xyz

+0

यह आपकी साइट पर वीडियो में मौजूद वार्ता में से एक था http://csharpindepth.com/Talks.aspx – rizzle

+0

हां, मुझे याद है कि मैंने वहां एक थ्रेडिंग संस्करण का उपयोग किया था, और फीडबैक सुझावों में से एक था धागे से बचें - ऊपर दिए गए उदाहरण की तरह उदाहरण का उपयोग करना स्पष्ट है। –

3

यह एक दिलचस्प सवाल है और ऐसा लगता है कि हमने देखा है लोगों को सभी विभिन्न तरीकों से जवाब देने के लिए मायने रखता है। मैं इस धारणा के तहत था कि दूसरा तरीका एकमात्र सुरक्षित तरीका होगा। यदि आप चलाने यह आप विकल्प 1 निश्चित रूप से सुरक्षित नहीं है देखेंगे

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

: मैं एक असली त्वरित सबूत मार पड़ी है।

+0

पूर्ण कार्यक्रम के लिए धन्यवाद :) – xyz

1

आपके मामले में, आप मैप करके नकल चाल का उपयोग किए बिना समस्या से बच सकते हैं अपने ListOfFoo धागे का एक अनुक्रम करने के लिए:

var threads = ListOfFoo.Select(foo => new Thread(() => foo.DoSomething())); 
foreach (var t in threads) 
{ 
    t.Start(); 
} 
संबंधित मुद्दे