在我寻求开发漂亮的数据驱动Silverlight应用程序时,我似乎不断遇到需要解决的某种竞争条件。最新的一个在下面。任何帮助,将不胜感激。Silverlight Combobox数据绑定竞赛条件
您在后端有两个表格:一个是组件,一个是制造商。每个组件都有一个制造商。根本不是一个不寻常的外键查找关系。
我Silverlight,我通过WCF服务访问数据。我将调用Components_Get(id)以获取Current组件(查看或编辑)以及致电Manufacturers_GetAll()以获取制造商的完整列表,以填充ComboBox的可能选择。然后,我将ComboBox上的SelectedItem绑定到当前组件的制造商和ComboBox上的ItemSource到可能制造商的列表中。像这样:
<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>
这时间最长的伟大的工作,直到我聪明,做组件的一个小客户端缓存(这是我的计划打开了制造商以及)。当我打开组件缓存并获得缓存命中时,所有数据都将正确存在于对象中,但SelectedItem将无法绑定。原因是Silverlight中的调用是异步的,并且缓存的好处是,Component不会在制造商之前返回。因此,当SelectedItem试图在ItemsSource列表中查找Components.Current.Manufacturer时,它不在那里,因为这个列表仍然是空的,因为Manufacturers.All尚未从WCF服务加载。同样,如果我关闭组件缓存,它会再次运行,但它感觉错误 - 就像我很幸运,时间正在运行。正确的修正恕我直言是为MS修复ComboBox/ItemsControl控件,以了解这将发生与异步调用是常态。但在那之前,我需要需要一种方式哟修复它...
这里是我想到的一些选项:
- 消除缓存或开启全线再次掩盖问题。不好恕我直言,因为这会再次失败。不是真的愿意把它打回地毯之下。
- 创建一个中介对象,为我做同步(这应该在ItemsControl本身完成)。它会接受Item和ItemList,然后在两者都到达时输出和ItemWithItemsList属性。我会将ComboBox绑定到结果输出,以便它永远不会获得一个项目在另一个之前。我的问题是,这似乎是一个痛苦,但它会确保竞争条件不会再发生。
任何thougnts /评论?
FWIW:我会在这里发布我的解决方案,为他人的利益。
@Joe:非常感谢回复。我知道只需从UI线程更新UI。这是我的理解,我想我已经通过调试器证实了这一点,即在SL2中,服务参考生成的代码为您处理此问题。即当我打电话Manufacturers_GetAll_Asynch()时,我通过Manufacturers_GetAll_Completed事件得到结果。如果您查看生成的服务引用代码,它可以确保从UI线程调用* Completed事件处理程序。我的问题不是这样,而是我做了两个不同的调用(一个用于制造商列表,另一个用于引用制造商ID的组件),然后将这两个结果绑定到一个ComboBox。它们都绑定在UI线程上,问题是如果列表在选择之前没有到达那里,那么选择将被忽略。
还要注意,这仍然是一个问题if you just set the ItemSource and the SelectedItem in the wrong order !!!
另一个更新: 虽然仍然存在组合框竞争条件,但我发现了其他有趣的东西。你应该永远不要从该属性的“getter”中生成一个PropertyChanged事件。例如:在我的类型为ManufacturerData的SL数据对象中,我有一个名为“All”的属性。在获取{}它检查,看它是否已被加载,如果没有加载它是这样的:
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 {}}方法中的“All”,通常会导致绑定系统再次调用All {get {}} ...我复制了这种从ScottGu博客发布方式创建可绑定silverlight数据对象的模式它总体上让我满意,但像这样的东西使它非常棘手。幸运的是修复很简单。希望这可以帮助别人。
该解决方案是一个常见的解决方案。很长一段时间,我一直在寻找覆盖所有选择器控件的更通用的解决方案;不只是ComboBoxes,而且不会从任何控制继承。有一种方法可以用行为来做到这一点。此提出的解决方案也适用于UWP,大概WPF:http://stackoverflow.com/questions/36003805/uwp-silverlight-combobox-selector-itemssource-selecteditem-race-condition-solu – 2016-03-15 22:35:20