这种types的CollectionView不支持从与分派器线程不同的线程对其SourceCollection的更改
我有一个DataGrid,它是通过asynchronous方法从ViewModel填充数据。我的DataGrid是:
<DataGrid ItemsSource="{Binding MatchObsCollection}" x:Name="dataGridParent" Style="{StaticResource EfesDataGridStyle}" HorizontalGridLinesBrush="#DADADA" VerticalGridLinesBrush="#DADADA" Cursor="Hand" AutoGenerateColumns="False" RowDetailsVisibilityMode="Visible" >
我正在使用http://www.amazedsaint.com/2010/10/asynchronous-delegate-command-for-your.html在我的viewmodel中实现asynchronous的方式。
这是我的viewmodel代码:
public class MainWindowViewModel:WorkspaceViewModel,INotifyCollectionChanged { MatchBLL matchBLL = new MatchBLL(); EfesBetServiceReference.EfesBetClient proxy = new EfesBetClient(); public ICommand DoSomethingCommand { get; set; } public MainWindowViewModel() { DoSomethingCommand = new AsyncDelegateCommand( () => Load(), null, null, (ex) => Debug.WriteLine(ex.Message)); _matchObsCollection = new ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC>(); } List<EfesBet.DataContract.GetMatchDetailsDC> matchList; ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC> _matchObsCollection; public ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC> MatchObsCollection { get { return _matchObsCollection; } set { _matchObsCollection = value; OnPropertyChanged("MatchObsCollection"); } } // public void Load() { matchList = new List<GetMatchDetailsDC>(); matchList = proxy.GetMatch().ToList(); foreach (EfesBet.DataContract.GetMatchDetailsDC match in matchList) { _matchObsCollection.Add(match); } }
正如你可以在我的ViewModel中的Load()方法中看到的,首先我从我的Service中获取matchList(这是一个DataContract类的列表)。然后通过foreach循环将我的matchList项插入到我的_matchObsCollection(这是一个ObservableCollection的DataContract类))。现在我在这里我得到上述错误(正如我在标题中所示)“这种types的CollectionView不支持从与分派器线程不同的线程更改其SourceCollection”
任何人都可以build议我任何解决scheme。而且,如果可能的话,我想知道如何绑定我的DataGrid在视图中,如果有更好的方法,也刷新它asynchronous。
由于您的ObservableCollection是在UI线程上创build的,因此您只能从UI线程修改它,而不能从其他线程修改它。 这被称为线程关联 。
如果您需要更新从不同线程在UI线程上创build的对象,只需put the delegate on UI Dispatcher
,那么您就可以将其委派给UI线程。 这将工作 –
public void Load() { matchList = new List<GetMatchDetailsDC>(); matchList = proxy.GetMatch().ToList(); foreach (EfesBet.DataContract.GetMatchDetailsDC match in matchList) { App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE { _matchObsCollection.Add(match); }); } }
如果我没有弄错,在WPF 4.5中,你应该可以做到这一点,没有任何问题。
现在要解决这个问题,你应该使用同步上下文。 在启动线程之前,必须将同步上下文存储在ui线程中。
var uiContext = SynchronizationContext.Current;
然后你在你的线程中使用它:
uiContext.Send(x => _matchObsCollection.Add(match), null);
看看这个tuto http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Parti-I
对于.NET 4.5+:你可以按照Daniel的答案。 在他的例子中,你给出版商负责,他们需要调用或调用正确的线程:
var uiContext = SynchronizationContext.Current; uiContext.Send(x => _matchObsCollection.Add(match), null);
或者你可以把你的服务/ viewmodel /任何的责任,只需启用CollectionSynchronization。 这样,如果您拨打电话,您不必担心您正在使用哪个线路以及您拨打哪一个线路。 责任是不是为发布者了。 (这可能会给你一点性能开销,但是在一个中央服务中这样做,它可以为你节省很多的exception,让你更容易的应用程序维护。)
private static object _lock = new object(); public MainWindowViewModel() { // ... _matchObsCollection = new ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC>(); BindingOperations.EnableCollectionSynchronization(_matchObsCollection , _lock); }
更多信息: https : //msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.enablecollectionsynchronization(v=vs.110).aspx
在Visual Studio 2015(Pro)中,转到Debug – > Windows – > Threads ,轻松地debugging并查看您所在的线程。
在我的情况(我填充ObservableCollection
与asynchronous任务,并没有访问App
实例)我使用TaskScheduler.FromCurrentSynchronizationContext()
来清理故障上的集合:
// some main task Task loadFileTask = Task.Factory.StartNew(...); Task cleanupTask = loadFileTask.ContinueWith( (antecedent) => { CleanupFileList(); }, /* do not cancel this task */ CancellationToken.None, /* run only if faulted main task */ TaskContinuationOptions.OnlyOnFaulted, /* use main SynchronizationContext */ TaskScheduler.FromCurrentSynchronizationContext());
如果您正在使用BackgroundWorker,则应该在UI的同一线程中引发事件 。
换句话说,如果你有两个视图A和B以及下面的代码A引发了事件WakeUpEvent
//Code inside codebehind or viewmodel of A var worker = new BackgroundWorker(); worker.DoWork += WorkerDoWork; //<-- Don't raise the event WakeUpEvent inside this method worker.RunWorkerCompleted += workerRunWorkerCompleted; // <-- Raise the event WakeUpEvent inside this method instead worker.RunWorkerAsync(); //Code inside codebehind or viewmodel of view B public ViewB () { WakeUpEvent += UpdateUICallBack; } private void UpdateUICallBack() { //Update here UI element }
WorkerDoWork方法在与UI不相同的线程中执行。
我曾经遇到同样的问题,并通过AsyncObservableCollection( http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/ )解决了这个问题。