web-dev-qa-db-ja.com

このタイプのCollectionViewは、Dispatcherスレッドとは異なるスレッドからのSourceCollectionへの変更をサポートしていません

私は非同期メソッドによってViewModelからデータを投入しているDataGridを持っています。私の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コードがあります:

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クラス)).. Nowここで私は上記のエラーを取得しています(タイトルで示したように)「このタイプのCollectionViewは、Dispatcherスレッドとは異なるスレッドからSourceCollectionへの変更をサポートしていません」 enter image description here

誰でも私に解決策を提案できますか?さらに、可能であればViewでDataGridをバインドする方法を知りたいし、より良い方法があれば非同期に更新することもできます。

122

ObservableCollectionは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);
            });
        }
    }
206
Rohit Vats

間違っていなければ、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

60
Daniel

あなたはこれを行うことができます:

App.Current.Dispatcher.Invoke((System.Action)delegate
             {
               _matchObsCollection.Add(match)
             });

.NET 4.5+の場合:ダニエルの答えに従うことができます。彼の例では、正しいスレッドで呼び出しまたは呼び出す必要があるという責任をパブリッシャーに与えます。

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

Visual Studio 2015(Pro)で[デバッグ]-> [Windows]-> [スレッド]に移動して、デバッグしているスレッドを確認します。

45
juFo

私は同じ問題を一度経験し、AsyncObservableCollectionの問題を解決しました( http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/ ) 。

6
mnyarar

私はここで解決策を見つけました: 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);
    }
}
3
Istvan Heckl

BackgroundWorkerを使用している場合は、UIの同じスレッドでイベントを発生させるにする必要があります。

つまり、AとBの2つのビューがあり、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とは異なるスレッドで実行されます。

2
Gianluca Conte

私の場合(非同期タスクを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());
2
Vladislav

私もこのエラーを受け取っていました:

「このタイプのCollectionViewは、Dispatcherスレッドとは異なるスレッドからのSourceCollectionへの変更をサポートしていません。」

「Release」構成のコピーである「Release Android」という名前の新しい構成を作成し、それを使用してArchive Managerで新しいリリースを作成していました。設定を「リリース」に戻し、すべてが正常に構築されました。これ以上のエラーはありません。

これが誰かを助けることを願っています。

0
Shane