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




multithreading winforms (16)

एक्शन वाई; // कक्षा के भीतर घोषित किया

label1.Invoke (y = () => label1.Text = "text");

मेरे पास एक परिदृश्य है। (विंडोज फॉर्म, सी #, .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 के निष्पादन का प्रभाव क्या है?

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

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


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

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");

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


पिछले उत्तरों के समान लाइनों के साथ, लेकिन एक बहुत छोटा जोड़ा जो क्रॉस थ्रेड इनोकेशन अपवाद के बिना सभी नियंत्रण गुणों का उपयोग करने की अनुमति देता है।

सहायक विधि

/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
    if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
    {
        a();
    }));
    else return false;

    return true;
}

नमूना उपयोग

// usage on textbox
public void UpdateTextBox1(String text)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
    textBox1.Text = ellapsed;
}

//Or any control
public void UpdateControl(Color c, String s)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
    myControl.Text = s;
    myControl.BackColor = c;
}

यह इस त्रुटि को हल करने का अनुशंसित तरीका नहीं है लेकिन आप इसे जल्दी दबा सकते हैं, यह काम करेगा। मैं प्रोटोटाइप या डेमो के लिए इसे पसंद करता हूं। जोड़ना

CheckForIllegalCrossThreadCalls = false

Form1() कन्स्ट्रक्टर में।


प्रेरक के अपडेट टिप्पणी के अनुसार (हटाए जाने के बाद):

मुझे लगता है कि मैंने सवाल ठीक से प्रस्तुत नहीं किया है।

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

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

जिस समाधान को आप चाहते हैं उसे इस तरह दिखना चाहिए:

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

नियंत्रण के थ्रेड पर वापस जाने का प्रयास करने से पहले अलग थ्रेड में अपनी गंभीर प्रसंस्करण करें। उदाहरण के लिए:

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}

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


Async / Await और कॉलबैक का उपयोग कर एक नया रूप। यदि आप अपनी परियोजना में विस्तार विधि रखते हैं तो आपको केवल कोड की एक पंक्ति की आवश्यकता है।

/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
    /// <summary>
    /// No more delegates, background workers etc. just one line of code as shown below
    /// Note it is dependent on the XTask class shown next.
    /// </summary>
    public async void ExampleMethod()
    {
        //Still on GUI/Original Thread here
        //Do your updates before the next line of code
        await XTask.RunAsync(() =>
        {
            //Running an asynchronous task here
            //Cannot update GUI Thread here, but can do lots of work
        });
        //Can update GUI/Original thread on this line
    }
}

/// <summary>
/// A class containing extension methods for the Task class 
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
    /// <summary>
    /// RunAsync is an extension method that encapsulates the Task.Run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

आप एक्स्टेंशन विधि में अन्य चीजें जोड़ सकते हैं जैसे कि इसे ट्राई / कैच स्टेटमेंट में लपेटना, कॉलर को यह बताने की इजाजत देता है कि पूरा होने के बाद किस प्रकार का रिटर्न करना है, कॉलर को अपवाद कॉलबैक:

कोशिश करें कैच, ऑटो अपवाद लॉगिंग और कॉलबैक जोड़ना

    /// <summary>
    /// Run Async
    /// </summary>
    /// <typeparam name="T">The type to return</typeparam>
    /// <param name="Code">The callback to the code</param>
    /// <param name="Error">The handled and logged exception if one occurs</param>
    /// <returns>The type expected as a competed task</returns>

    public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
    {
       var done =  await Task<T>.Run(() =>
        {
            T result = default(T);
            try
            {
               result = Code("Code Here");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unhandled Exception: " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Error(ex);
            }
            return result;

        });
        return done;
    }
    public async void HowToUse()
    {
       //We now inject the type we want the async routine to return!
       var result =  await RunAsync<bool>((code) => {
           //write code here, all exceptions are logged via the wrapped try catch.
           //return what is needed
           return someBoolValue;
       }, 
       error => {

          //exceptions are already handled but are sent back here for further processing
       });
        if (result)
        {
            //we can now process the result because the code above awaited for the completion before
            //moving to this statement
        }
    }

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

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

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


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


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

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

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

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


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

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

यूआई थ्रेड

  • केवल एक थ्रेड (यूआई थ्रेड) है, जिसे 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 ।


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

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

fsw.SynchronizingObject = this

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





invoke