此类型的 CollectionView 不支持从与 Dispatcher 线程不同的线程更改其 SourceCollection

我有一个 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在我的视图模型中实现异步方式。

下面是我的视图模型代码:

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 获得 match List (一个 DataContractClass 的列表)。然后通过 foreach 循环,我将 match List 项插入到 my _ match ObsCollection (它是 DataContractClass 的 Observer ableCollection)中。现在我得到了上面的错误(正如我在标题中所显示的)“这种 CollectionView 不支持从与 Dispatcher 线程不同的线程更改它的 SourceCollection” enter image description here

有人能给我提点建议吗。此外,如果可能的话,我想知道如何在视图中绑定我的 DataGrid,如果有更好的方法,还可以异步刷新它。

114455 次浏览

如果我没有记错的话,在 WPF 4.5中,您应该可以毫无问题地完成这项工作。

现在,为了解决这个问题,您应该使用同步上下文。在启动线程之前,必须将同步上下文存储在 ui 线程中。

var uiContext = SynchronizationContext.Current;

然后你在你的帖子里用它:

uiContext.Send(x => _matchObsCollection.Add(match), null);

看看这个图图 Http://www.codeproject.com/articles/31971/understanding-synchronizationcontext-part-i

因为您的 Observer ableCollection 是在 UI 线程上创建的,所以只能从 UI 线程修改它,而不能从其他线程修改它。这被称为 线程亲和力

如果您需要更新来自不同线程的 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);
});
}
}

我曾经遇到过同样的问题,并且使用 AsyncObserver ableCollection (http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/)解决了这个问题。

在我的例子中(我使用异步任务填充 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());

你可以这样做:

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

或者您可以将责任放到您的服务/视图模型/无论什么,并简单地启用 CollectionSynchronization。这样,如果你打了一个电话,你就不用担心你在哪个线程上,你在哪个线程上打电话。责任不再由发布者承担。 (这可能会给您带来一点性能开销,但是在中央服务中进行这项工作,可以节省大量异常,并使您更容易维护应用程序。)

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

在 VisualStudio2015(Pro)中,转到 调试—— > Windows —— > 线程以便轻松调试并查看所在的线程。

如果您正在使用 Background Worker,那么您应该在 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 不同的线程中执行。

我也得到了这个错误:

“此类型的 CollectionView 不支持从与 Dispatcher 线程不同的线程更改其 SourceCollection”

原来我已经创建了一个名为“ ReleasAndroid”的新配置,它是“ ReleaseAndroid”配置的副本,并且正在用它在 Archive Manager 中创建新版本。我改回了配置“发布”,一切都构建得很好。别再犯错了。

希望这对谁有帮助。

我在这里找到了一个解决办法: Https://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/ 你只需要创建一个新的类并使用它来代替 Observer ableCollection,这对我来说很有用。

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