我有一个DataGrid,它通过异步方法从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中实现异步方式。
这是我的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(其中)是一个DataContract类的ObservableCollection))。现在我得到上面的错误(正如我在标题中所示)“这种类型的CollectionView不支持从不同于Dispatcher线程的线程更改其SourceCollection”
任何人都可以向我推荐任何解决方案。如果可能的话,我想知道如何在View中绑定我的DataGrid,并且如果有更好的方法,也可以异步刷新它。
答案 0 :(得分:202)
由于您的ObservableCollection是在UI线程上创建的,因此您只能从UI线程而不是从其他线程修改它。这被称为thread affinity。
如果您需要更新从不同线程在UI线程上创建的对象,只需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);
});
}
}
答案 1 :(得分:55)
如果我没弄错,在WPF 4.5中,您应该可以毫无问题地执行此操作。
现在要解决此问题,您应该使用同步上下文。在启动线程之前,必须将同步上下文存储在ui线程中。
var uiContext = SynchronizationContext.Current;
然后你在你的线程中使用它:
uiContext.Send(x => _matchObsCollection.Add(match), null);
看看这个tuto http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I
答案 2 :(得分:42)
你可以这样做:
App.Current.Dispatcher.Invoke((System.Action)delegate
{
_matchObsCollection.Add(match)
});
对于.NET 4.5+:您可以按照Daniel的回答。在他的示例中,您要向发布者提供他们需要在正确的线程上调用或调用的责任:
var uiContext = SynchronizationContext.Current;
uiContext.Send(x => _matchObsCollection.Add(match), null);
或者您可以将责任放在您的service / viewmodel / whatever上,并简单地启用CollectionSynchronization。这样,如果您拨打电话,则无需担心您所在的线路以及您拨打电话的线路。责任不再适用于发布者。 (这可能会给您带来一点性能开销,但在中央服务中执行此操作,它可以为您节省大量异常并为您提供更轻松的应用程序维护。)
private static object _lock = new object();
public MainWindowViewModel()
{
// ...
_matchObsCollection = new ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC>();
BindingOperations.EnableCollectionSynchronization(_matchObsCollection , _lock);
}
在Visual Studio 2015(专业版)中,转到调试 - &gt; Windows - &gt;线程可以轻松调试并查看您所在的线程。
答案 3 :(得分:5)
我曾经历过同样的问题,并使用AsyncObservableCollection(http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/)解决了这个问题。
答案 4 :(得分:2)
在我的情况下(我使用异步任务填充ObservableCollection
并且无法访问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());
答案 5 :(得分:2)
如果您使用的是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不同的线程中执行。
答案 6 :(得分:2)
我在这里找到了解决方案: https://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/ 您只需创建一个新类并使用它代替ObservableCollection。它对我有用。
public class AsyncObservableCollection<T> : ObservableCollection<T>
{
private SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
public AsyncObservableCollection()
{
}
public AsyncObservableCollection(IEnumerable<T> list)
: base(list)
{
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the CollectionChanged event on the current thread
RaiseCollectionChanged(e);
}
else
{
// Raises the CollectionChanged event on the creator thread
_synchronizationContext.Send(RaiseCollectionChanged, e);
}
}
private void RaiseCollectionChanged(object param)
{
// We are in the creator thread, call the base implementation directly
base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the PropertyChanged event on the current thread
RaisePropertyChanged(e);
}
else
{
// Raises the PropertyChanged event on the creator thread
_synchronizationContext.Send(RaisePropertyChanged, e);
}
}
private void RaisePropertyChanged(object param)
{
// We are in the creator thread, call the base implementation directly
base.OnPropertyChanged((PropertyChangedEventArgs)param);
}
}
答案 7 :(得分:0)
我也收到此错误:
“这种类型的CollectionView不支持从与Dispatcher线程不同的线程对其SourceCollection进行更改”
结果是,我创建了一个名为“ Release Android”的新配置,该配置是“ Release”配置的副本,并使用该配置在存档管理器中创建新版本。我改回配置为“ Release”,一切正常。没有更多错误。
希望这对某人有帮助。