Silverlight Combobox-Binding race-condition

In meinem Bestreben zu entwickeln, eine ziemlich datengesteuerten silverlight-app, ich scheine immer wieder an eine Art race-condition, die umgangen werden muss. Die neueste ist unten. Jede Hilfe würde geschätzt werden.

Haben Sie zwei Tabellen auf die back-end: ein is-Komponenten und einer ist-Herstellern. Jede Komponente hat EINEN Hersteller. Gar nicht ungewöhnlich, foreign key lookup-Beziehung.

Ich Silverlight habe ich Zugriff auf die Daten via WCF service. Ich werde einen Anruf tätigen, um Components_Get(id), um die Aktuelle Komponente (anzeigen oder Bearbeiten) und ein Aufruf Manufacturers_GetAll (), um die komplette Liste der Hersteller, die zum Auffüllen der möglichen Auswahl für ein Kombinationsfeld. Ich habe dann Binden Sie die SelectedItem auf die ComboBox-Komponente auf die Hersteller, die für die Aktuelle Komponente und die ItemSource auf das ComboBox-Steuerelement die Liste der möglichen Hersteller. wie diese:

<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>

Dieser funktioniert großartig für die längste Zeit, bis ich clever und habe ein kleines client-side caching der Komponente (die ich geplante Wende für die Hersteller wie auch). Als ich mich umdrehte, Zwischenspeicherung, die für die Komponente ein, und ich bekam einen cache-Treffer, die alle von den Daten wäre es in die Objekte korrekt, aber die SelectedItem-fehlschlagen würde, zu Binden. Der Grund dafür ist, dass die Aufrufe Asynchron sind in Silverlight und mit dem Vorteil der Zwischenspeicherung, die Komponente wird nicht zurückgegeben wird, bevor der Hersteller. Also, wenn Sie die SelectedItem-versucht zu finden, die Komponenten.Aktuelle.Hersteller in der ItemsSource-Liste-es gibt Sie nicht, weil diese Liste ist noch leer, denn die Hersteller.Alle noch nicht geladenen von den WCF-Dienst noch. Wieder, wenn ich schalten Sie die Komponente-caching, funktioniert es wieder, aber es fühlt sich FALSCH an - wie bin ich nur immer Glück, dass das timing funktioniert. Der richtige fix IMHO ist für MS zu beheben ComboBox/ItemsControl-Steuerelement zu verstehen, dass dies geschehen WIRD, mit Asynch anrufen die norm. Aber bis dahin brauche ich einen brauchen einen Weg yo es beheben...

Hier sind einige Möglichkeiten, die ich gedacht haben:

  1. Beseitigen Sie die caching-oder schalten Sie ihn über das board noch einmal-Maske das problem. Nicht Gut, IMHO, denn es wird wieder scheitern. Nicht wirklich bereit zu kehren es unter den Teppich.
  2. Erstellen Sie ein Zwischenobjekt, das die Synchronisierung für mich (das sollte getan werden, im ItemsControl selbst). Sie akzeptieren würden und Item und ein ItemsList und dann auf Ausgang und ItemWithItemsList Eigenschaft, wenn beide eine angekommen. Ich würde Binden Sie die ComboBox-Komponente auf die resultierende Ausgabe, so dass es nie ein Element vor dem anderen. Mein problem ist, dass dies scheint wie ein Schmerz, aber es wird sicherstellen, dass die race-condition nicht wieder auftreten.

Jede thougnts/Kommentare?

FWIW: ich poste meine Lösung hier für das wohl anderer.

@Joe: vielen Dank für die Antwort. Ich bin der Notwendigkeit bewusst, für die Aktualisierung der Benutzeroberfläche nur vom UI-thread. Es ist mein Verständnis, und ich glaube, ich habe bestätigt dies durch den debugger, der in SL2, der code generiert, durch den die Service-Referenz übernimmt dies für Sie. d.h. wenn ich Anrufe, Manufacturers_GetAll_Asynch(), bekomme ich das Ergebnis durch die Manufacturers_GetAll_Completed Veranstaltung. Wenn Sie sich die Service-Referenz-code, der generiert wird, es sorgt dafür, dass die *Completed-Ereignis-handler wird aufgerufen, aus dem UI-thread. Mein problem ist nicht dieses, es ist, dass ich zwei verschiedene Anrufe (eines für die Hersteller-Liste und eine für die Komponente mit Verweis auf eine id eines Herstellers) und Binden Sie diese beiden Ergebnisse zu einem einzigen ComboBox. Beide Binden an den UI-thread, das problem ist, dass wenn die Liste nicht bekommen, es vor der Auswahl, wird die Auswahl ignoriert.

Beachten Sie auch, dass dies immer noch ein problem wenn Sie nur die ItemSource und das SelectedItem in der falschen Reihenfolge!!!

Weiteres Update:
Es gibt zwar immer noch die combobox-race-Bedingung, entdeckte ich etwas anderes Interessantes. Sollten Sie NIE genrate ein PropertyChanged-Ereignis in der "getter" für diese Eigenschaft. Beispiel: in meinem SL-Daten-Objekt von Typ ManufacturerData, habe ich eine Eigenschaft namens "Alle". In der Get{} es wird überprüft, um zu sehen, wenn es geladen wurde, wenn nicht lädt es so:

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

}

Beachten Sie, dass dieser code FEHL auf einem "cache-hit", weil es erzeugt ein PropertyChanged-Ereignis für "Alle" innerhalb der Alle { Get {}} Methode, die normalerweise dazu führen würde das Bindungs-System zu nennen, Alle {get{}} wieder...ich kopiert dieses Muster zu erstellen bindbare silverlight-Daten-Objekte aus einem ScottGu blog-posting Weg zurück und es war mir insgesamt gut, aber Zeug wie das macht es ziemlich knifflig. Zum Glück ist die Lösung ist einfach. Hoffe, dies hilft jemand anderes.

InformationsquelleAutor caryden | 2009-02-27
Schreibe einen Kommentar