首页 文章

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

提问于
浏览
2

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

当我尝试将项目添加到 ObservableCollection 时,我一直收到异常,因为我'm adding them from another background thread after some processing is done. It'是 NotSupportedException ,说 Additional information: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

这是我正在使用的线程:

  • 线程A是WPF UI线程

  • 线程B是用于创建视图模型(和ObservableCollection)的工作线程

  • Thread C是另一个将项添加到ObservableCollection的工作线程

我试图将我的ViewModel与我的视图分开,所以我不想(不想)使用 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>

3 回答

  • 0

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

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

  • 0

    我想你可以在计时器中重写代码

    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");       
                      });
                }
            }
    
  • 1

    使用System.Windows.Threading.DispatcherTimer将解决您的问题 . 但我建议重写代码并使用基于任务的异步操作来执行长时间运行的初始化任务 . 你想用计时器解决什么问题?

相关问题