2009-02-27 16 views
6

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

आपके पास बैक एंड पर दो टेबल हैं: एक घटक है और एक निर्माता है। प्रत्येक घटक के पास एक निर्माता होता है। बिल्कुल असामान्य, विदेशी कुंजी लुकअप-रिश्ते नहीं।

मैं सिल्वरलाइट, मैं डब्ल्यूसीएफ सेवा के माध्यम से डेटा तक पहुंचता हूं। कॉम्बोबॉक्स के संभावित चयनों को पॉप्युलेट करने के लिए निर्माताओं की पूरी सूची प्राप्त करने के लिए मैं मौजूदा घटक (देखने या संपादित करने) और निर्माता/GetAll() को कॉल करने के लिए Components_Get (id) पर कॉल करूंगा। इसके बाद मैं मौजूदा घटक के लिए कॉम्बोबॉक्स पर कॉम्बोबॉक्स पर चयनित इटैम और संभावित निर्माता की सूची में कॉम्बोबॉक्स पर आइटमसोर्स को बांधता हूं। इस तरह:

<UserControl.Resources> 
    <data:WebServiceDataManager x:Key="WebService" /> 
</UserControl.Resources> 
<Grid DataContext={Binding Components.Current, mode=OneWay, Source={StaticResource WebService}}> 
    <ComboBox Grid.Row="2" Grid.Column="2" Style="{StaticResource ComboBoxStyle}" Margin="3" 
       ItemsSource="{Binding Manufacturers.All, Mode=OneWay, Source={StaticResource WebService}}" 
       SelectedItem="{Binding Manufacturer, Mode=TwoWay}" > 
     <ComboBox.ItemTemplate> 
      <DataTemplate> 
       <Grid> 
        <TextBlock Text="{Binding Name}" Style="{StaticResource DefaultTextStyle}"/> 
       </Grid> 
      </DataTemplate> 
     </ComboBox.ItemTemplate> 
    </ComboBox> 
</Grid> 

यह लंबे समय के लिए महान काम किया, जब तक मैं चालाक हो गया और घटक के एक छोटे से ग्राहक साइड संचय (जो मैं निर्माता के लिए पर बारी के रूप में अच्छी तरह से योजना बनाई) किया था। जब मैंने घटक के लिए कैशिंग चालू कर दिया और मुझे कैश हिट मिला, तो सभी डेटा सही ढंग से ऑब्जेक्ट्स में होंगे, लेकिन चयनित इटिम बांधने में विफल रहेगा। इसका कारण यह है कि कॉल सिल्वरलाइट में असीमित है और कैशिंग के लाभ के साथ, निर्माता को निर्माताओं से पहले वापस नहीं किया जा रहा है। तो जब चयनित इटम्स आइटमसोर्स सूची में घटक .Current.Manufacturer को खोजने का प्रयास करता है, तो यह वहां नहीं है, क्योंकि यह सूची अभी भी खाली है क्योंकि निर्माता। सभी अभी तक डब्ल्यूसीएफ सेवा से लोड नहीं हुए हैं। दोबारा, अगर मैं घटक कैशिंग बंद कर देता हूं, तो यह फिर से काम करता है, लेकिन यह गलत लगता है - जैसे कि मैं सिर्फ भाग्यशाली हो रहा हूं कि समय काम कर रहा है। सही फिक्स आईएमएचओ एमएस के लिए कॉम्बोबॉक्स/आइटम नियंत्रण नियंत्रण को ठीक करने के लिए है यह समझने के लिए कि असिन्च कॉल मानक के साथ होगा।

  1. कैशिंग को हटा दें या बोर्ड भर में इसे चालू करने के लिए एक बार फिर से नकाब: लेकिन तब तक, मैं एक की जरूरत के लिए एक रास्ता यो इसे ठीक ...

    यहां कुछ विकल्प है कि मैं के बारे में सोचा है की जरूरत है समस्या। अच्छा IMHO नहीं, क्योंकि यह फिर से असफल हो जाएगा। गलीचा के नीचे इसे वापस साफ करने के लिए वास्तव में तैयार नहीं है।

  2. एक मध्यस्थ वस्तु बनाएं जो मेरे लिए सिंक्रनाइज़ेशन करेगी (जो आइटम नियंत्रण में ही किया जाना चाहिए)। यह स्वीकार करेगा और आइटम और एक आइटम सूची और फिर आउटपुट और ItemWithItemsList संपत्ति जब दोनों पहुंचे हैं। मैं परिणामस्वरूप आउटपुट के लिए कॉम्बोबॉक्स को बांध दूंगा ताकि यह किसी दूसरे के सामने कभी भी एक आइटम न मिले। मेरी समस्या यह है कि यह दर्द की तरह लगता है लेकिन यह सुनिश्चित करेगा कि दौड़ की स्थिति फिर से नहीं होती है।

कोई भी संकेत/टिप्पणियां?

FWIW: मैं दूसरों के लाभ के लिए यहां अपना समाधान पोस्ट करूंगा।

@ जो: प्रतिक्रिया के लिए बहुत बहुत धन्यवाद। मैं केवल UI थ्रेड से यूआई को अपडेट करने की आवश्यकता से अवगत हूं। यह मेरी समझ है और मुझे लगता है कि मैंने डीबगर के माध्यम से यह पुष्टि की है कि एसएल 2 में, सेवा संदर्भ द्वारा उत्पन्न कोड आपके लिए इसका ख्याल रखता है। यानी जब मैं निर्माता/GetAll_Asynch() को कॉल करता हूं, तो मुझे निर्माता_GetAll_Completed ईवेंट के माध्यम से परिणाम मिलता है। यदि आप उत्पन्न होने वाले सेवा संदर्भ कोड के अंदर देखते हैं, तो यह सुनिश्चित करता है कि * पूर्ण ईवेंट हैंडलर को यूआई थ्रेड से बुलाया जाता है। मेरी समस्या यह नहीं है, यह है कि मैं दो अलग-अलग कॉल करता हूं (एक निर्माता सूची के लिए और एक घटक के लिए एक जो घटक की आईडी का संदर्भ देता है) और फिर इन दोनों परिणामों को एक कॉम्बोबॉक्स में बांधें। वे यूआई थ्रेड पर दोनों बांधते हैं, समस्या यह है कि अगर सूची चयन से पहले नहीं मिलती है, तो चयन को नजरअंदाज कर दिया जाता है।

यह भी ध्यान दें कि यह अभी भी एक समस्या है if you just set the ItemSource and the SelectedItem in the wrong order !!!

एक और अद्यतन: जबकि अभी भी combobox दौड़ की स्थिति है, मैंने कुछ और दिलचस्प खोजा। आपको कभी उस संपत्ति के लिए "गेटटर" के भीतर से एक संपत्ति चेंज किए गए ईवेंट को जनरेट करना चाहिए। उदाहरण: मेरे एसएल डेटा ऑब्जेक्ट में निर्माता डेटाटा में, मेरे पास "ऑल" नामक एक संपत्ति है।

public class ManufacturersData : DataServiceAccessbase 
{ 
    public ObservableCollection<Web.Manufacturer> All 
    { 
     get 
     { 
      if (!AllLoaded) 
       LoadAllManufacturersAsync(); 
      return mAll; 
     } 
     private set 
     { 
      mAll = value; 
      OnPropertyChanged("All"); 
     } 
    } 

    private void LoadAllManufacturersAsync() 
    { 
     if (!mCurrentlyLoadingAll) 
     { 
      mCurrentlyLoadingAll = true; 

      // check to see if this component is loaded in local Isolated Storage, if not get it from the webservice 
      ObservableCollection<Web.Manufacturer> all = IsoStorageManager.GetDataTransferObjectFromCache<ObservableCollection<Web.Manufacturer>>(mAllManufacturersIsoStoreFilename); 
      if (null != all) 
      { 
       UpdateAll(all); 
       mCurrentlyLoadingAll = false; 
      } 
      else 
      { 
       Web.SystemBuilderClient sbc = GetSystemBuilderClient(); 
       sbc.Manufacturers_GetAllCompleted += new EventHandler<hookitupright.com.silverlight.data.Web.Manufacturers_GetAllCompletedEventArgs>(sbc_Manufacturers_GetAllCompleted); 
       sbc.Manufacturers_GetAllAsync(); ; 
      } 
     } 
    } 
    private void UpdateAll(ObservableCollection<Web.Manufacturer> all) 
    { 
     All = all; 
     AllLoaded = true; 
    } 
    private void sbc_Manufacturers_GetAllCompleted(object sender, hookitupright.com.silverlight.data.Web.Manufacturers_GetAllCompletedEventArgs e) 
    { 
     if (e.Error == null) 
     { 
      UpdateAll(e.Result.Records); 
      IsoStorageManager.CacheDataTransferObject<ObservableCollection<Web.Manufacturer>>(e.Result.Records, mAllManufacturersIsoStoreFilename); 
     } 
     else 
      OnWebServiceError(e.Error); 
     mCurrentlyLoadingAll = false; 
    } 

} 

ध्यान दें कि यह कोड एक "कैश हिट" पर विफल रहता है क्योंकि यह एक PropertyChanged घटना उत्पन्न करेगा: मिल में {} यह अगर यह लोड किया गया है को देखने के लिए, नहीं तो यह इसे इस तरह से लोड करता है की जाँच करता है ऑल {Get {}} विधि के भीतर से "ऑल" के लिए जो आमतौर पर बाध्यकारी सिस्टम को सभी {get {}} को कॉल करने का कारण बनता है ... मैंने स्कॉटगू ब्लॉग पोस्टिंग तरीके से बाइंडबल रजत रोशनी डेटा ऑब्जेक्ट्स बनाने के इस पैटर्न की प्रतिलिपि बनाई और उसने मुझे अच्छी तरह से सेवा दी है, लेकिन इस तरह की चीजें इसे बहुत मुश्किल बनाती हैं। सौभाग्य से फिक्स सरल है। मनाइए कि यह किसी और के लिए सहायक हो।

उत्तर

7

ठीक है मुझे जवाब मिला है (कॉम्बोबॉक्स कैसे काम करता है यह जानने के लिए बहुत सारे परावर्तक का उपयोग करके)।

समस्या तब मौजूद है जब चयनित स्रोत सेट के बाद आइटम स्रोत सेट किया गया है। जब ऐसा होता है तो Combobx इसे चयन के पूर्ण रीसेट के रूप में देखता है और चयनित इटैम/चयनित इंडेक्स को साफ़ करता है। आप (ComboBox के लिए आधार वर्ग) System.Windows.Controls.Primitives.Selector में यहाँ यह देख सकते हैं:

protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) 
{ 
    base.OnItemsChanged(e); 
    int selectedIndex = this.SelectedIndex; 
    bool flag = this.IsInit && this._initializingData.IsIndexSet; 
    switch (e.Action) 
    { 
     case NotifyCollectionChangedAction.Add: 
      if (!this.AddedWithSelectionSet(e.NewStartingIndex, e.NewStartingIndex + e.NewItems.Count)) 
      { 
       if ((e.NewStartingIndex <= selectedIndex) && !flag) 
       { 
        this._processingSelectionPropertyChange = true; 
        this.SelectedIndex += e.NewItems.Count; 
        this._processingSelectionPropertyChange = false; 
       } 
       if (e.NewStartingIndex > this._focusedIndex) 
       { 
        return; 
       } 
       this.SetFocusedItem(this._focusedIndex + e.NewItems.Count, false); 
      } 
      return; 

     case NotifyCollectionChangedAction.Remove: 
      if (((e.OldStartingIndex > selectedIndex) || (selectedIndex >= (e.OldStartingIndex + e.OldItems.Count))) && (e.OldStartingIndex < selectedIndex)) 
      { 
       this._processingSelectionPropertyChange = true; 
       this.SelectedIndex -= e.OldItems.Count; 
       this._processingSelectionPropertyChange = false; 
      } 
      if ((e.OldStartingIndex <= this._focusedIndex) && (this._focusedIndex < (e.OldStartingIndex + e.OldItems.Count))) 
      { 
       this.SetFocusedItem(-1, false); 
       return; 
      } 
      if (e.OldStartingIndex < selectedIndex) 
      { 
       this.SetFocusedItem(this._focusedIndex - e.OldItems.Count, false); 
      } 
      return; 

     case NotifyCollectionChangedAction.Replace: 
      if (!this.AddedWithSelectionSet(e.NewStartingIndex, e.NewStartingIndex + e.NewItems.Count)) 
      { 
       if ((e.OldStartingIndex <= selectedIndex) && (selectedIndex < (e.OldStartingIndex + e.OldItems.Count))) 
       { 
        this.SelectedIndex = -1; 
       } 
       if ((e.OldStartingIndex > this._focusedIndex) || (this._focusedIndex >= (e.OldStartingIndex + e.OldItems.Count))) 
       { 
        return; 
       } 
       this.SetFocusedItem(-1, false); 
      } 
      return; 

     case NotifyCollectionChangedAction.Reset: 
      if (!this.AddedWithSelectionSet(0, base.Items.Count) && !flag) 
      { 
       this.SelectedIndex = -1; 
       this.SetFocusedItem(-1, false); 
      } 
      return; 
    } 
    throw new InvalidOperationException(); 
} 

नोट पिछले मामले - रीसेट ... जब आप लोड एक नया ItemSource आप अंत यहां तक ​​और कोई भी चयनित इटैम/चयनित इंडेक्स उड़ गया है?!?

वैसे समाधान अंत में बहुत आसान था। मैंने बस गलती कॉम्बोबॉक्स को उप-वर्गीकृत किया और निम्नानुसार इस विधि के लिए प्रदान किया और ओवरराइड किया। हालांकि मैं जोड़ने के लिए किया था एक:

public class FixedComboBox : ComboBox 
{ 
    public FixedComboBox() 
     : base() 
    { 
     // This is here to sync the dep properties (OnSelectedItemChanged is private is the base class - thanks M$) 
     base.SelectionChanged += (s, e) => { FixedSelectedItem = SelectedItem; }; 
    } 

    // need to add a safe dependency property here to bind to - this will store off the "requested selectedItem" 
    // this whole this is a kludgy wrapper because the OnSelectedItemChanged is private in the base class 
    public readonly static DependencyProperty FixedSelectedItemProperty = DependencyProperty.Register("FixedSelectedItem", typeof(object), typeof(FixedComboBox), new PropertyMetadata(null, new PropertyChangedCallback(FixedSelectedItemPropertyChanged))); 
    private static void FixedSelectedItemPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
    { 
     FixedComboBox fcb = obj as FixedComboBox; 
     fcb.mLastSelection = e.NewValue; 
     fcb.SelectedItem = e.NewValue; 
    } 
    public object FixedSelectedItem 
    { 
     get { return GetValue(FixedSelectedItemProperty); } 
     set { SetValue(FixedSelectedItemProperty, value);} 
    } 
    protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 
    { 
     base.OnItemsChanged(e); 
     if (-1 == SelectedIndex) 
     { 
      // if after the base class is called, there is no selection, try 
      if (null != mLastSelection && Items.Contains(mLastSelection)) 
       SelectedItem = mLastSelection; 
     } 
    } 

    protected object mLastSelection = null; 
} 
सभी

कि यह करता है (क) वर्ष SelectedItem और उसके बाद बंद बचाने (ख) की जाँच है कि अगर ItemsChanged के बाद, अगर हम बना नहीं चयन और पुराने SelectedItem है नई सूची में मौजूद है ... अच्छा ... इसे चुना गया!

+0

यह समाधान एक आम है। लंबे समय तक, मैं एक और सामान्य समाधान की तलाश कर रहा हूं जिसमें सभी चयनकर्ता नियंत्रण शामिल हैं; न केवल कॉम्बोबॉक्स, और किसी भी नियंत्रण से विरासत के बिना ऐसा करता है। व्यवहार के साथ ऐसा करने का एक तरीका है। यह प्रस्तावित समाधान यूडब्ल्यूपी में भी काम करता है, और शायद डब्ल्यूपीएफ: http://stackoverflow.com/questions/36003805/uwp-silverlight-combobox-selector-itemssource-selecteditem-race-condition-solu –

0

यह आपके पोस्ट से स्पष्ट नहीं है कि आप जानते हैं कि आपको UI थ्रेड पर यूआई तत्वों को संशोधित करना होगा - या आपको समस्याएं होंगी। यहां एक संक्षिप्त उदाहरण दिया गया है जो पृष्ठभूमि थ्रेड बनाता है जो वर्तमान समय के साथ टेक्स्टबॉक्स को संशोधित करता है।

कुंजी MyTextBox.Dispather.BeginInvoke Page.xaml.cs. में है।

Page.xaml:

<UserControl x:Class="App.Page" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300" 
      Loaded="UserControl_Loaded"> 
    <Grid x:Name="LayoutRoot"> 
     <TextBox FontSize="36" Text="Just getting started." x:Name="MyTextBox"> 
     </TextBox> 
    </Grid> 
</UserControl> 

Page.xaml.cs:

using System; 
using System.Windows; 
using System.Windows.Controls; 

namespace App 
{ 
    public partial class Page : UserControl 
    { 
     public Page() 
     { 
      InitializeComponent(); 
     } 

     private void UserControl_Loaded(object sender, RoutedEventArgs e) 
     { 
      // Create our own thread because it runs forever. 
      new System.Threading.Thread(new System.Threading.ThreadStart(RunForever)).Start(); 
     } 

     void RunForever() 
     { 
      System.Random rand = new Random(); 
      while (true) 
      { 
       // We want to get the text on the background thread. The idea 
       // is to do as much work as possible on the background thread 
       // so that we do as little work as possible on the UI thread. 
       // Obviously this matters more for accessing a web service or 
       // database or doing complex computations - we do this to make 
       // the point. 
       var now = System.DateTime.Now; 
       string text = string.Format("{0}.{1}.{2}.{3}", now.Hour, now.Minute, now.Second, now.Millisecond); 

       // We must dispatch this work to the UI thread. If we try to 
       // set MyTextBox.Text from this background thread, an exception 
       // will be thrown. 
       MyTextBox.Dispatcher.BeginInvoke(delegate() 
       { 
        // This code is executed asynchronously on the 
        // Silverlight UI Thread. 
        MyTextBox.Text = text; 
       }); 
       // 
       // This code is running on the background thread. If we executed this 
       // code on the UI thread, the UI would be unresponsive. 
       // 
       // Sleep between 0 and 2500 millisends. 
       System.Threading.Thread.Sleep(rand.Next(2500)); 
      } 
     } 
    } 
} 

तो, आप चीजों को अतुल्यकालिक रूप से प्राप्त करना चाहते हैं, तो आप Control.Dispatcher.BeginInvoke का उपयोग करने के बारे में सूचित करना होगा यूआई तत्व है कि आपके पास कुछ नया डेटा है।

+0

मैं यूआई थ्रेड से यूआई अद्यतन करने की आवश्यकता के बारे में पता कर रहा हूँ। मुझे इस पर मूल प्रश्न में संपादित करें (यहां सीमित स्थान और यह वहां लगाने के लिए वारंट लग रहा था)। – caryden

0

आइटमसोर्स को पुनर्निर्मित करने की बजाय प्रत्येक बार यह आपके लिए एक अवलोकन योग्य चयन <> पर बाध्य करने के लिए आसान होता और फिर उस पर साफ़() को कॉल करें और (...) सभी तत्व जोड़ें। इस तरह बाध्यकारी रीसेट नहीं है।

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

आप अभी भी वर्तमान आइटम की आईडी (या जो कुछ भी इसे विशिष्ट रूप से परिभाषित करता है) प्राप्त कर सकते हैं, नियंत्रण को दोबारा बांधें और फिर बाध्य सूची में आइटम को उसी आईडी के साथ ढूंढें और उस आइटम को वर्तमान के रूप में बाध्य करें।

1

कैस्केडिंग कम्बोबॉक्स बनाने के दौरान मैंने इसी मुद्दे से संघर्ष किया, और किसी ऐसे व्यक्ति के ब्लॉग पोस्ट में ठोकर खाई जिसने एक आसान लेकिन आश्चर्यजनक फिक्स पाया। InstamesSource को सेट करने के बाद अपडेटलेआउट() को कॉल करें लेकिन चयनित इटैम सेट करने से पहले। डाटाबेस को पूरा होने तक कोड को ब्लॉक करने के लिए मजबूर होना चाहिए। मैं बिल्कुल यकीन है कि क्यों यह यह ठीक करता है, लेकिन मैं के बाद से फिर से रेस स्थिति का अनुभव नहीं किया है या नहीं ...

इस जानकारी का स्रोत: http://compiledexperience.com/Blog/post/Gotcha-when-databinding-a-ComboBox-in-Silverlight.aspx

2

जब मैं पहली बार इस समस्या का सामना किया मैं नाराज था, लेकिन मुझे लगा कि इसके चारों ओर एक रास्ता होना था। अब तक मेरा सबसे अच्छा प्रयास पोस्ट में विस्तृत है।

http://blogs.msdn.com/b/kylemc/archive/2010/06/18/combobox-sample-for-ria-services.aspx

मैं के रूप में यह निम्नलिखित की तरह कुछ करने के लिए वाक्य रचना संकुचित बहुत खुश थी।

<ComboBox Name="AComboBox" 
     ItemsSource="{Binding Data, ElementName=ASource}" 
     SelectedItem="{Binding A, Mode=TwoWay}" 
     ex:ComboBox.Mode="Async" /> 

केली

+0

धन्यवाद केली। मैंने जो कुछ भी आजमाया है वह पूर्व है: कॉम्बोबॉक्स.मोड = "असिंसेगर" लेकिन इसने बाधा को हटा दिया कि चयनित इटैम को आइटमसोर्स से पहले सेट करने की आवश्यकता है जो यहां वर्णित कई समस्याओं का मूल प्रतीत होता है। क्या आपको पता है कि सिल्वरलाइट 5 में आने वाला मूल समाधान क्या होगा? –

+0

मुझे नहीं पता कि एसएल 5 में क्या होगा, लेकिन मैंने इन पंक्तियों के साथ कुछ भी नहीं सुना है। –

0

मामले में आप यहाँ आने क्योंकि आप एक Combobox चयन समस्या है, जिसका अर्थ है, कुछ भी नहीं होता है जब आप सूची में अपने आइटम पर क्लिक करें। ध्यान दें कि निम्न संकेत भी आपकी मदद कर सकता है:

1/सुनिश्चित करें कि आप इस मामले में कुछ भी सूचित न करें आप किसी आइटम का चयन कर

public string SelectedItem 
     { 
      get 
      { 
       return this.selectedItem; 
      } 
      set 
      { 
       if (this.selectedItem != value) 
       { 
        this.selectedItem = value; 
        //this.OnPropertyChanged("SelectedItem"); 
       } 
      } 
     } 

2/यकीन है कि आइटम आपके द्वारा चुने गए अंतर्निहित डेटा स्रोत में अब भी है बनाना मामले में आप इसे दुर्घटना

मैं दोनों गलतियां द्वारा हटाना;)

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