将项目添加到在后台线程上创建的 ObservableCollection

Add items to ObservableCollection that was created on a background thread

在我的应用程序中,ViewModels 需要一些时间来初始化,所以我在启动时在后台线程上创建它们,以保持主要 window 响应。

当我尝试将项目添加到 ObservableCollection 时,我总是遇到异常,因为我是在完成某些处理后从另一个后台线程添加它们的。这是一个 NotSupportedException,表示 Additional information: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

这是我正在使用的线程:

我试图让我的 ViewModels 与我的视图分开,所以我不想在我的视图模型中引用 WPF Dispatcher,所以我不能(不想)使用 Dispatcher.Invoke.

我尝试使用 BindingOperations.EnableCollectionSynchronization,但似乎只有在从 WPF UI 线程调用时才有效。

如何同步对我的 ObservableCollection 的访问,以便我可以从后台线程向其添加项目?有没有一种简单、优雅的方法来做到这一点?最好不要使用第三方库?

我创建了一个小例子来说明问题:

AppViewModel.cs:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Data;
using System.Windows.Input;

namespace WpfObservableTest
{
    public class AppViewModel
    {
        private System.Timers.Timer _timer;

        public ObservableCollection<string> Items { get; private set; }

        private object _itemsLock = new object();

        public AppViewModel()
        {
            Console.WriteLine("AppViewModel ctor thread id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);

            Items = new ObservableCollection<string>();
            BindingOperations.EnableCollectionSynchronization(Items, _itemsLock);

            _timer = new Timer(500);
            _timer.Elapsed += _timer_Elapsed;
        }

        public void StartTimer()
        {
            _timer.Start();
        }

        private void _timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            Console.WriteLine("TimerElapsed thread id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);
            lock (_itemsLock)
            {
                Items.Add("Test");
            }
        }        
    }
}

MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfObservableTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private AppViewModel _viewModel;

        public MainWindow()
        {
            Task initTask = Task.Run(() => { _viewModel = new AppViewModel(); });
            initTask.Wait();

            DataContext = _viewModel;

            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            _viewModel.StartTimer();
        }
    }
}

MainWindow.xaml:

<Window x:Class="WpfObservableTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Button Click="Button_Click" Grid.Row="0">Start</Button>
        <ListView ItemsSource="{Binding Items}" Grid.Row="1">

        </ListView>
    </Grid>
</Window>

我认为这里的问题是您使用的是 System.Timers.Timer。这将在 non-UI 线程上触发。相反,请考虑使用将在 UI 线程上触发的 DispatcherTimer

如果这只是突出问题的示例代码,那么在您的实际代码中使用 Dispatcher.BeginInvoke 以便在 UI 线程上执行加法。

我认为你可以在定时器中重新编写代码

            private void _timer_Elapsed(object sender, ElapsedEventArgs e)
            {
            Console.WriteLine("TimerElapsed thread id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);
            lock (_itemsLock)
            {    
              System.Windows.Application.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,(Action)delegate()
                  {
                     Items.Add("Test");       
                  });
            }
        }

使用 System.Windows.Threading.DispatcherTimer 可以解决您的问题。但我建议重写您的代码并使用基于任务的异步操作来完成您的长 运行 初始化任务。 你到底想用定时器解决什么问题?