c# - क्रॉस-थ्रेड ऑपरेशन मान्य नहीं है: इसे बनाए गए थ्रेड के अलावा किसी थ्रेड से नियंत्रित नियंत्रण




multithreading winforms (14)

यूआई में थ्रेडिंग मॉडल

बुनियादी अवधारणाओं को समझने के लिए कृपया यूआई अनुप्रयोगों में थ्रेडिंग मॉडल पढ़ें। लिंक पृष्ठ पर नेविगेट करता है जो डब्ल्यूपीएफ थ्रेडिंग मॉडल का वर्णन करता है। हालांकि, विंडोज फॉर्म एक ही विचार का उपयोग करता है।

यूआई थ्रेड

  • केवल एक थ्रेड (यूआई थ्रेड) है, जिसे System.Windows.Forms.Control और इसके उप-वर्ग सदस्यों तक पहुंचने की अनुमति है।
  • UI थ्रेड की तुलना में विभिन्न थ्रेड से System.Windows.Forms.Control सदस्य तक पहुंचने का प्रयास क्रॉस-थ्रेड अपवाद का कारण बन जाएगा।
  • चूंकि केवल एक धागा है, इसलिए सभी यूआई ऑपरेशंस उस थ्रेड में कार्य आइटम के रूप में कतारबद्ध हैं:

  • यदि यूआई थ्रेड के लिए कोई काम नहीं है, तो निष्क्रिय निष्क्रिय हैं जिनका उपयोग यूआई से संबंधित कंप्यूटिंग द्वारा किया जा सकता है।
  • उल्लिखित अंतराल का उपयोग System.Windows.Forms.Control.Invoke लिए System.Windows.Forms.Control.Invoke या System.Windows.Forms.Control.BeginInvoke विधियों का उपयोग करें:

BeginInvoke और विधियों को आमंत्रित करें

  • विधि के कंप्यूटिंग ओवरहेड को छोटा होने के साथ-साथ इवेंट हैंडलर विधियों के कंप्यूटिंग ओवरहेड होना चाहिए क्योंकि यूआई थ्रेड का उपयोग किया जाता है - वही जो उपयोगकर्ता इनपुट को संभालने के लिए ज़िम्मेदार है। भले ही यह System.Windows.Forms.Control.Invoke या System.Windows.Forms.Control.BeginInvoke
  • कंप्यूटिंग महंगी ऑपरेशन करने के लिए हमेशा अलग थ्रेड का उपयोग करें। चूंकि .NET 2.0 BackgroundWorker विंडोज प्रपत्रों में कंप्यूटिंग महंगे संचालन करने के लिए समर्पित है। हालांकि नए समाधानों में आपको here वर्णित एसिंक-प्रतीक्षा पैटर्न का उपयोग करना चाहिए।
  • System.Windows.Forms.Control.Invoke या System.Windows.Forms.Control.BeginInvoke विधियों का उपयोग केवल उपयोगकर्ता इंटरफ़ेस को अद्यतन करने के लिए करें। यदि आप भारी गणना के लिए उनका उपयोग करते हैं, तो आपका आवेदन अवरुद्ध होगा:

आह्वान

  • System.Windows.Forms.Control.Invoke अलग थ्रेड का कारण बनता है जब तक कि आवंटित विधि पूरी नहीं हो जाती:

BeginInvoke

  • System.Windows.Forms.Control.BeginInvoke अलग थ्रेड का इंतजार नहीं करता है जब तक कि लागू विधि पूरी नहीं हो जाती है:

संहिता समाधान

प्रश्न पर उत्तर पढ़ें सी # में किसी अन्य थ्रेड से जीयूआई को कैसे अपडेट करें? । सी # 5.0 और .NET 4.5 के लिए अनुशंसित समाधान here ।

मेरे पास एक परिदृश्य है। (विंडोज फॉर्म, सी #, .NET)

  1. एक मुख्य रूप है जो कुछ उपयोगकर्ता नियंत्रण होस्ट करता है।
  2. उपयोगकर्ता नियंत्रण कुछ भारी डेटा ऑपरेशन करता है, जैसे कि अगर मैं सीधे UserControl_Load विधि को कॉल करता हूं तो यूआई लोड विधि निष्पादन की अवधि के लिए गैर-प्रतिक्रियाशील बन जाता है।
  3. इसे दूर करने के लिए मैं अलग-अलग धागे पर डेटा लोड करता हूं (मौजूदा कोड को जितना छोटा कर सकता हूं उसे बदलने की कोशिश कर रहा हूं)
  4. मैंने एक पृष्ठभूमि कार्यकर्ता थ्रेड का उपयोग किया जो डेटा लोड करेगा और जब पूरा किया जाएगा तो उसने उस एप्लिकेशन को सूचित किया होगा जिसने अपना काम किया है।
  5. अब एक असली समस्या आई है। सभी यूआई (मुख्य रूप और उसके बच्चे उपयोगकर्ता नियंत्रण) प्राथमिक मुख्य धागे पर बनाया गया था। उपयोगकर्ता नियंत्रण की लोड विधि में मैं उपयोगकर्ता नियंत्रण पर कुछ नियंत्रण (जैसे टेक्स्टबॉक्स) के मानों के आधार पर डेटा ला रहा हूं।

छद्म कोड इस तरह दिखेगा:

कोड 1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

यह अपवाद था

क्रॉस-थ्रेड ऑपरेशन मान्य नहीं है: इसे बनाए गए थ्रेड के अलावा किसी थ्रेड से नियंत्रित नियंत्रण।

इसके बारे में और जानने के लिए मैंने कुछ googling किया और एक सुझाव निम्नलिखित कोड का उपयोग करने की तरह आया था

कोड 2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it wont give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

लेकिन लेकिन लेकिन ... ऐसा लगता है कि मैं वापस वर्ग में हूँ। आवेदन फिर से गैर जिम्मेदार बन गया। ऐसा लगता है कि अगर स्थिति # 1 के निष्पादन के कारण हो। लोडिंग कार्य फिर से पैरेंट थ्रेड द्वारा किया जाता है और तीसरा नहीं जो मैंने उत्पन्न किया था।

मुझे नहीं पता कि मुझे यह सही या गलत लगता है या नहीं। मैं थ्रेडिंग के लिए नया हूँ।

मैं इसे कैसे हल करूं और ब्लॉक # 1 के निष्पादन का प्रभाव क्या है?

स्थिति यह है : मैं नियंत्रण के मूल्य के आधार पर डेटा को वैश्विक चर में लोड करना चाहता हूं। मैं बाल धागे से नियंत्रण के मूल्य को बदलना नहीं चाहता हूं। मैं इसे कभी भी बच्चे के थ्रेड से नहीं कर रहा हूं।

तो केवल मूल्य तक पहुंच ताकि डेटा से संबंधित डेटा प्राप्त किया जा सके।


.NET में नियंत्रण आमतौर पर थ्रेड सुरक्षित नहीं होते हैं। इसका मतलब है कि आपको उस स्थान के अलावा किसी धागे से नियंत्रण तक नहीं पहुंचना चाहिए। इसके आस-पास पहुंचने के लिए, आपको नियंत्रण का आह्वान करने की आवश्यकता है, जो आपका दूसरा नमूना प्रयास कर रहा है।

हालांकि, आपके मामले में जो कुछ भी आपने किया है वह लंबे समय तक चलने वाली विधि को मुख्य धागे पर पास कर देता है। बेशक, यह वास्तव में आप नहीं करना चाहते हैं। आपको इसे थोड़ा सा पुनर्विचार करने की आवश्यकता है ताकि आप मुख्य धागे पर जो कुछ भी कर रहे हैं वह यहां और वहां एक त्वरित संपत्ति स्थापित कर रहा है।


आपको पृष्ठभूमि कार्यकर्ता उदाहरण को देखने की आवश्यकता है:
BackgroundWorker विशेष रूप से यह यूआई परत के साथ कैसे इंटरैक्ट करता है। आपके पोस्टिंग के आधार पर, ऐसा लगता है कि यह आपके मुद्दों का उत्तर देता है।


उदाहरण के लिए यूआई थ्रेड के नियंत्रण से टेक्स्ट प्राप्त करने के लिए:

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
         text = CStr(ctl.Invoke(New GetControlTextInvoker(AddressOf GetControlText), _
                           ctl))
    Else
        text = ctl.Text
    End If

Return text
End Function

किसी अन्य धागे से वस्तुओं को संशोधित करने के लिए सबसे सरल (मेरी राय में) का पालन करें:

using System.Threading.Tasks;
using System.Threading;

namespace TESTE
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Action<string> DelegateTeste_ModifyText = THREAD_MOD;
            Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
        }

        private void THREAD_MOD(string teste)
        {
            textBox1.Text = teste;
        }
    }
}

क्रॉस थ्रेड ऑपरेशंस के लिए दो विकल्प हैं।

Control.InvokeRequired Property 

और दूसरा एक उपयोग करने के लिए है

SynchronizationContext Post Method

Control.InvokeRequired केवल तभी उपयोगी होता है जब नियंत्रण कक्षा से विरासत में नियंत्रण नियंत्रण होता है जबकि सिंक्रनाइज़ेशन कॉन्टेक्स्ट कहीं भी उपयोग किया जा सकता है। कुछ उपयोगी जानकारी निम्न लिंक के रूप में है

क्रॉस थ्रेड अद्यतन यूआई | नेट

सिंक्रनाइज़ेशन कॉन्टेक्स्ट का उपयोग कर क्रॉस थ्रेड अपडेट यूआई नेट


मुझे FileSystemWatcher साथ यह समस्या हुई है और पाया है कि निम्न कोड समस्या हल कर चुके हैं:

fsw.SynchronizingObject = this

नियंत्रण तब घटनाओं से निपटने के लिए वर्तमान फॉर्म ऑब्जेक्ट का उपयोग करता है, और इसलिए उसी धागे पर होगा।


मुझे अब बहुत देर हो चुकी है। हालांकि आज भी अगर आपको क्रॉस थ्रेड नियंत्रणों तक पहुंचने में परेशानी हो रही है? आज तक यह सबसे छोटा जवाब है: पी

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

इस तरह मैं थ्रेड से किसी भी फॉर्म नियंत्रण तक पहुंचता हूं।


मुझे चेक-एंड-इनवोक कोड मिलता है जिसे फ़ॉर्म से संबंधित सभी विधियों के भीतर बहुत ही वर्बोज़ और अनइडेड होने के लिए परेशान करने की आवश्यकता होती है। यहां एक सरल विस्तार विधि है जो आपको इसे पूरी तरह से दूर करने देती है:

public static class Extensions
{
    public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del) 
        where TControlType : Control
        {
            if (control.InvokeRequired)
                control.Invoke(new Action(() => del(control)));
            else
                del(control);
    }
}

और फिर आप बस यह कर सकते हैं:

textbox1.Invoke(t => t.Text = "A");

चारों ओर गड़बड़ नहीं - सरल।


यदि आप जिस ऑब्जेक्ट के साथ काम कर रहे हैं उसके पास कोई वैकल्पिक तरीका नहीं है

(InvokeRequired)

यह उपयोगी है यदि आप मुख्य रूप से किसी अन्य वस्तु के साथ किसी अन्य वर्ग के मुख्य वर्ग के साथ मुख्य रूप में काम कर रहे हैं, लेकिन मुख्य रूप से InvokeRequired नहीं है

delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);

private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
    MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}

public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
    objectWithoutInvoke.Text = text;
}

यह उपर्युक्त जैसा ही काम करता है, लेकिन यदि आपके पास अनावश्यक के साथ कोई ऑब्जेक्ट नहीं है, तो यह एक अलग दृष्टिकोण है, लेकिन मेनफॉर्म तक पहुंच है


यूआई को बदलने के लिए आवश्यक न्यूनतम काम के लिए आप केवल Invoke या BeginInvoke का उपयोग करना चाहते हैं। आपकी "भारी" विधि को किसी अन्य थ्रेड पर निष्पादित करना चाहिए (उदाहरण के लिए पृष्ठभूमिवर्कर के माध्यम से) लेकिन फिर UI को अपडेट करने के लिए Control.Invoke / Control.BeginInvoke का उपयोग करना। इस तरह आपका यूआई थ्रेड यूआई इवेंट इत्यादि को संभालने के लिए स्वतंत्र होगा।

WinForms उदाहरण के लिए मेरा थ्रेडिंग आलेख देखें - हालांकि पृष्ठभूमि वर्कर्स दृश्य पर पहुंचने से पहले लेख लिखा गया था, और मुझे डर है कि मैंने इसे उस सम्मान में अपडेट नहीं किया है। BackgroundWorker केवल कॉलबैक को थोड़ा सा सरल बनाता है।


यूआई क्रॉस-थ्रेडिंग मुद्दों के लिए सबसे साफ (और उचित) समाधान सिंक्रनाइज़ेशन कॉन्टेक्स्ट का उपयोग करना है, बहु-थ्रेडेड एप्लिकेशन आलेख में UI को कॉल सिंक्रनाइज़ करना देखें, यह बहुत अच्छी तरह से बताता है।


वही प्रश्न: कैसे-से-अपडेट-द-गुई-से-थ्रेड-इन-सी

दो तरीके:

  1. E.result में वापसी मूल्य और पृष्ठभूमि में टेक्स्टबॉक्स मान सेट करने के लिए इसका उपयोग करें Worker_RunWorker पूर्ण घटना

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

उदाहरण:

public  class data_holder_for_controls
{
    //it will hold value for your label
    public  string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();
    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread 
    }

    public static void perform_logic()
    {
        //put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            //statements here
        }
        //set result in status variable
        d1.status = "Task done";
    }
}

this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));




invoke