2011-08-16 12 views
6

इस समस्या को समझाने के लिए मैंने सबकुछ एक छोटे नमूना अनुप्रयोग में जरूरी रखा जो उम्मीद है कि समस्या बताती है। मैंने वास्तव में जितनी कम संभव लाइनों में सबकुछ धक्का देने की कोशिश की, लेकिन मेरे असली आवेदन में ये अलग-अलग कलाकार एक-दूसरे को नहीं जानते और यह भी नहीं करना चाहिए। तो, सरल उत्तर "चर को कुछ पंक्तियों को ऊपर ले जाएं और उस पर आमंत्रित करें" काम नहीं करेगा।बाइंडिंगसोर्स और क्रॉस-थ्रेड अपवाद

तो आइए कोड के साथ शुरू करें और बाद में थोड़ा और स्पष्टीकरण दें। सबसे पहले एक साधारण वर्ग है जो INotifyProperty लागू करता है:

public class MyData : INotifyPropertyChanged 
{ 
    private string _MyText; 

    public MyData() 
    { 
     _MyText = "Initial"; 
    } 

    public string MyText 
    { 
     get { return _MyText; } 

     set 
     { 
      _MyText = value; 
      PropertyChanged(this, new PropertyChangedEventArgs("MyText")); 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
} 

तो कुछ खास नहीं है। और यहाँ उदाहरण कोड जो केवल किसी भी खाली सांत्वना आवेदन परियोजना में रखा जा सकता है:

static void Main(string[] args) 
{ 
    // Initialize the data and bindingSource 
    var myData = new MyData(); 
    var bindingSource = new BindingSource(); 
    bindingSource.DataSource = myData; 

    // Initialize the form and the controls of it ... 
    var form = new Form(); 

    // ... the TextBox including data bind to it 
    var textBox = new TextBox(); 
    textBox.DataBindings.Add("Text", bindingSource, "MyText"); 
    textBox.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged; 
    textBox.Dock = DockStyle.Top; 
    form.Controls.Add(textBox); 

    // ... the button and what happens on a click 
    var button = new Button(); 
    button.Text = "Click me"; 
    button.Dock = DockStyle.Top; 
    form.Controls.Add(button); 

    button.Click += (_, __) => 
    { 
     // Create another thread that does something with the data object 
     var worker = new BackgroundWorker(); 

     worker.RunWorkerCompleted += (___, ____) => button.Enabled = true; 
     worker.DoWork += (___, _____) => 
     { 
      for (int i = 0; i < 10; i++) 
      { 
       // This leads to a cross-thread exception 
       // but all i'm doing is simply act on a property in 
       // my data and i can't see here that any gui is involved. 
       myData.MyText = "Try " + i; 
      } 
     }; 

     button.Enabled = false; 
     worker.RunWorkerAsync(); 
    }; 

    form.ShowDialog(); 
} 

आप इस कोड को चलाने के हैं, तो आप MyText संपत्ति को बदलने के लिए कोशिश कर रहा द्वारा एक क्रॉस-धागा अपवाद मिलेगा। यह आता है, MyData ऑब्जेक्ट PropertyChanged पर कॉल करता है जिसे BindindSource द्वारा पकड़ा जाएगा। यह Binding के अनुसार, TextBox की संपत्ति को अपडेट करने का प्रयास करेगा। जो स्पष्ट रूप से अपवाद की ओर जाता है।

मेरे सबसे बड़ी समस्या यहां तथ्य यह है कि MyData वस्तु एक जीयूआई बारे में कुछ पता नहीं करना चाहिए (कारण यह एक सरल डेटा वस्तु है) से आता है। इसके अलावा कार्यकर्ता थ्रेड को गुई के बारे में कुछ नहीं पता है। यह बस डेटा ऑब्जेक्ट्स के समूह पर कार्य करता है और उन्हें कुशल बनाता है।

आईएमएचओ मुझे लगता है कि BindingSource को यह पता होना चाहिए कि प्राप्त करने वाला ऑब्जेक्ट कौन सा थ्रेड रहता है और मूल्य प्राप्त करने के लिए Invoke() एप्राइपियेट करें। दुर्भाग्यवश यह इस में निर्मित नहीं है (या मैं गलत हूं?), इसलिए मेरा प्रश्न है:

डेटा क्रॉस-थ्रेड अपवाद को कैसे हल किया जा सकता है यदि डेटा ऑब्जेक्ट और न ही वर्कर थ्रेड को बाध्यकारी स्रोत के बारे में कुछ पता है जो सुन रहा है उनके कार्यक्रमों के लिए डेटा को एक गुई में धक्का देना।

उत्तर

4

यहाँ ऊपर के उदाहरण है कि इस समस्या का हल का हिस्सा है। इस समाधान की कमी यह है कि आपको टेक्स्टबॉक्स में दिखाए गए किसी भी मध्यवर्ती परिणाम नहीं मिलेगा, लेकिन यह कुछ भी नहीं है।

2

यदि आप Winforms नियंत्रण से बंधे हैं तो आप किसी अन्य थ्रेड से बाइंडिंग स्रोत को अपडेट नहीं कर सकते हैं। आपके MyText सेटर में आपको Invoke संपत्ति को सीधे चलाने के बजाए यूआई थ्रेड पर बदल दिया जाना चाहिए।

यदि आप अपनी माईटेक्स्ट क्लास और बाइंडिंगसोर्स के बीच अमूर्तता की एक अतिरिक्त परत चाहते हैं तो आप ऐसा कर सकते हैं, लेकिन आप यूआई थ्रेड से बाइंडिंगसोर्स को अलग नहीं कर सकते हैं।

+1

इस्तेमाल किया लेकिन समस्या यह है कि मैं 'MyData' वर्ग के भीतर यूआई धागा नहीं जानता है। तो यूआई थ्रेड का आह्वान कैसे करें यदि मेरे पास उस पर किसी भी नियंत्रण तक पहुंच नहीं है जो वर्तमान में 'Invoke()' को कॉल करने के लिए फॉर्म पर है? – Oliver

+0

@ ओलिवर: अरे आपने इस मुद्दे को हल किया है? मैं इस तरह से कुछ भी अटक गया हूँ? –

+0

@ माहेश: इस सवाल का अपना जवाब जोड़ें, मैंने इसे कैसे हल किया। यह सही नहीं है, लेकिन कुछ भी नहीं है। – Oliver

0

आप पृष्ठभूमि थ्रेड से प्रगति की रिपोर्ट करने का प्रयास कर सकते हैं जो यूआई थ्रेड में एक ईवेंट को बढ़ाएगा। वैकल्पिक रूप से, आप DoWork पर कॉल करने से पहले वर्तमान संदर्भ (आपका यूआई थ्रेड) याद रखने का प्रयास कर सकते हैं और फिर DoWork के अंदर आप डेटा पोस्ट करने के लिए याद किए गए संदर्भ का उपयोग कर सकते हैं।

button.Click += (_, __) => 
{ 
    // Create another thread that does something with the data object 
    var worker = new BackgroundWorker(); 

    worker.DoWork += (___, _____) => 
    { 
     for (int i = 0; i < 10; i++) 
     { 
      // This doesn't lead to any cross-thread exception 
      // anymore, cause the binding source was told to 
      // be quiet. When we're finished and back in the 
      // gui thread tell her to fire again its events. 
      myData.MyText = "Try " + i; 
     } 
    }; 

    worker.RunWorkerCompleted += (___, ____) => 
    { 
     // Back in gui thread let the binding source 
     // update the gui elements. 
     bindingSource.ResumeBinding(); 
     button.Enabled = true; 
    }; 

    // Stop the binding source from propagating 
    // any events to the gui thread. 
    bindingSource.SuspendBinding(); 
    button.Enabled = false; 
    worker.RunWorkerAsync(); 
}; 

तो यह किसी भी पार धागा अपवाद को अब और नहीं ले जाता है:

+0

दुर्भाग्य से मेरा असली एप्लिकेशन यह पृष्ठभूमिवर्कर नहीं है जो यूआई थ्रेड से शुरू हुआ है जो डेटा अपडेट कर रहा है। वास्तव में यह एक धागा है जिसमें वस्तु की एक सूची है और बस उन पर काम करता है। ये वस्तुएं दूसरी सूची में भी हैं, जो एक डाटाग्रिड से बंधी हुई हैं। तो ऑब्जेक्ट स्वयं कैसे जानता है कि वे कुछ घटना श्रोताओं हैं जिन्हें यूई धागे पर कार्य करना है? क्या यह सुनिश्चित करने के लिए रिसीवर की ज़िम्मेदारी नहीं है कि वे सही संदर्भ में ईवेंट डेटा को संसाधित करते हैं? – Oliver

+0

@ ओलिवर मुझे लगता है कि आप सही हैं और यह उन रिसीवर पर निर्भर है जिन्हें पता होना चाहिए कि डेटा को कहां संसाधित करना है। क्या आप घटनाओं के माध्यम से ऐसा करने की कोशिश कर सकते हैं? आप डेटा रिसीव किए गए ईवेंट को परिभाषित कर सकते हैं और UI से इसकी सदस्यता ले सकते हैं (रिपोर्टिंग प्रगति के समान)। सूची में ऑब्जेक्ट्स किसी भी श्रोताओं से अवगत नहीं हैं, वे सिर्फ ऑब्जेक्ट के थ्रेड से ईवेंट को आग लगाते हैं, और सीएलआर रिसीवर के संदर्भों को स्वचालित रूप से प्रबंधित करेगा। यदि आप घटनाओं का उपयोग करते हैं, तो निगरानी करने की कोई आवश्यकता नहीं है कि कौन सी थ्रेड आग और जो प्राप्त होता है। नहीं? – oleksii

+0

ऑब्जेक्ट बस अपने ईवेंट को अपने धागे में सही करता है, सही। फिर बाध्यकारी स्रोत इसे प्राप्त करता है (ऑब्जेक्ट थ्रेड के भीतर) और स्वचालित रूप से लक्ष्य को अपडेट करने का प्रयास करता है (ऑब्जेक्ट थ्रेड के भीतर भी) जो क्रॉस-थ्रेड अपवाद की ओर जाता है। बाइंडिंग स्रोत यूई धागे से संबंधित है, लेकिन यह जांच नहीं करता है कि यह यूई धागे से आग लगती है और मुझे लगता है कि यह माइक्रोसॉफ्ट द्वारा डिज़ाइन दोष है, है ना? – Oliver

1

मुझे एहसास है कि आपका प्रश्न कुछ समय पहले सामने आया था, लेकिन मैंने जवाब देने का फैसला किया है, अगर वह वहां किसी के लिए सहायक हो।

मेरा सुझाव है कि आप अपने मुख्य एप्लिकेशन के भीतर मेरीडेटा की संपत्ति में बदलाव की घटना को बदलने पर विचार करें, फिर अपना यूआई अपडेट करें। यहां यह दिखाई दे सकता है:

//This delegate will help us access the UI thread 
delegate void dUpdateTextBox(string text); 

//You'll need class-scope references to your variables 
private MyData myData; 
private TextBox textBox; 

static void Main(string[] args) 
{ 
    // Initialize the data and bindingSource 
    myData = new MyData(); 
    myData.PropertyChanged += MyData_PropertyChanged; 

    // Initialize the form and the controls of it ... 
    var form = new Form(); 

    // ... the TextBox including data bind to it 
    textBox = new TextBox(); 
    textBox.Dock = DockStyle.Top; 
    form.Controls.Add(textBox); 

    // ... the button and what happens on a click 
    var button = new Button(); 
    button.Text = "Click me"; 
    button.Dock = DockStyle.Top; 
    form.Controls.Add(button); 

    button.Click += (_, __) => 
    { 
     // Create another thread that does something with the data object 
     var worker = new BackgroundWorker(); 

     worker.RunWorkerCompleted += (___, ____) => button.Enabled = true; 
     worker.DoWork += (___, _____) => 
     { 
      for (int i = 0; i < 10; i++) 
      { 
       myData.MyText = "Try " + i; 
      } 
     }; 

     button.Enabled = false; 
     worker.RunWorkerAsync(); 
    }; 

    form.ShowDialog(); 
} 

//This handler will be called every time "MyText" is changed 
private void MyData_PropertyChanged(Object sender, PropertyChangedEventArgs e) 
{ 
    if((MyData)sender == myData && e.PropertyName == "MyText") 
    { 
     //If we are certain that this method was called from "MyText", 
     //then update the UI 
     UpdateTextBox(((MyData)sender).MyText); 
    } 
} 

private void UpdateTextBox(string text) 
{ 
    //Check to see if this method call is coming in from the UI thread or not 
    if(textBox.RequiresInvoke) 
    { 
     //If we're not on the UI thread, invoke this method from the UI thread 
     textBox.BeginInvoke(new dUpdateTextBox(UpdateTextBox), text); 
     return; 
    } 

    //If we've reached this line of code, we are on the UI thread 
    textBox.Text = text; 
} 

अनुमोदित, यह बाध्यकारी पैटर्न से दूर है जो आप पहले कोशिश कर रहे थे। हालांकि MyText के लिए प्रत्येक अपडेट प्राप्त किया जाना चाहिए और बिना किसी समस्या के प्रदर्शित किया जाना चाहिए।

0

Windows में

Froms पार धागा में मैं सिर्फ

// this = from on which listbox control is created. 
this.Invoke(new Action(() => { SomeBindingSource.ResetBindings(false); })); 

और देखा

संबंधित मुद्दे