पहचानकर्ता और बंद हो जाता है


79

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

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

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

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

अपडेट: जैसा कि जॉन स्कीट के जवाब में बताया गया है, इसका विशेष रूप से थ्रेडिंग से कोई लेना-देना नहीं है।


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


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

जवाबों:


103

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


C # 5 से पहले

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

के साथ foreach, चर को लूप के बाहर घोषित किया जाता है - यानी

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

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

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

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


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

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

@Orlangur मैंने सालों से इस पर एरिक, मैड्स और एंडर्स के साथ सीधी बातचीत की है। संकलक ने कल्पना का पालन किया तो सही था। कल्पना ने एक चुनाव किया। बस: उस विकल्प को बदल दिया गया।
मार्क Gravell

यह उत्तर C # 4 पर लागू होता है, लेकिन बाद के संस्करणों के लिए नहीं: "C # 5 में, फ़ॉरच का लूप वेरिएबल लूप के अंदर तार्किक रूप से होगा, और इसलिए क्लोज़र हर बार वेरिएबल की एक नई प्रति को बंद कर देगा।" ( एरिक लिपर्ट )
डगलस

@ डगलस ऐ, मैं इनको ठीक कर रहा हूं क्योंकि हम जाते हैं, लेकिन यह एक सामान्य ठोकर थी, इसलिए: बहुत कुछ जाना है!
मार्क Gravell

37

पॉप कैटलिन और मार्क ग्रेवेल के उत्तर सही हैं। सभी जो मैं जोड़ना चाहता हूं, वह क्लोजर के बारे में मेरे लेख की एक कड़ी है (जो जावा और सी # दोनों के बारे में बात करता है)। बस सोचा था कि यह थोड़ा मूल्य जोड़ सकता है।

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

यह आपकी साइट csharpindepth.com/Talks.aspx
missaghi

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

यह जानकर अच्छा लगा कि वीडियो हालांकि देखे जा रहे हैं :)
जॉन स्कीट

यहां तक ​​कि यह समझते हुए कि चर forपाश के बाहर मौजूद है यह व्यवहार मुझे भ्रमित कर रहा है। उदाहरण के लिए क्लोजर व्यवहार के आपके उदाहरण में, stackoverflow.com/a/428624/20774 , बंद के बाहर मौजूद चर अभी भी सही ढंग से बांधता है। यह अलग क्यों है?
जेम्स मैकमोहन

17

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

C # और उसके परिणामों में अनाम विधियों का कार्यान्वयन (भाग 1)

C # और उसके परिणामों में अनाम विधियों का कार्यान्वयन (भाग 2)

C # और उसके परिणामों में अनाम विधियों का कार्यान्वयन (भाग 3)

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

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


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

3

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

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

यदि आप इसे चलाते हैं तो आपको विकल्प 1 दिखाई देगा निश्चित रूप से सुरक्षित नहीं है।


1

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

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


-5
Foo f2 = f;

के रूप में एक ही संदर्भ के लिए अंक

f 

इसलिए कुछ भी नहीं खोया और कुछ भी हासिल नहीं हुआ ...


1
यह जादू नहीं है। यह बस पर्यावरण पर कब्जा कर लेता है। यहाँ और छोरों के लिए समस्या यह है कि कैप्चर चर को उत्परिवर्तित (फिर से असाइन) किया जाता है।
लीपी 16

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

3
@ इलेप्पी: मैं यहां कोनराड के साथ हूं। कंपाइलर की लंबाई जादू की तरह महसूस होती है, और शब्दार्थ स्पष्ट रूप से परिभाषित होने के बावजूद वे अच्छी तरह से समझ नहीं पाते हैं। किसी भी चीज के बारे में पुरानी कहावत अच्छी तरह से समझ में नहीं आती है कि वह जादू की तुलना में है
जॉन स्कीट

2
@Jon Skeet क्या आपका मतलब है "कोई भी पर्याप्त रूप से उन्नत तकनीक जादू से
अविभाज्य है

2
यह एक संदर्भ की ओर इशारा नहीं करता है। यह एक संदर्भ है। यह एक ही वस्तु की ओर इशारा करता है, लेकिन यह एक अलग संदर्भ है।
mqp
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.