将项目添加到在后台线程上创建的 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.
这是我正在使用的线程:
- 线程 A 是 WPF UI 线程
- 线程 B 是用于创建视图模型(和 ObservableCollection)的工作线程
- 线程 C 是另一个将项目添加到 ObservableCollection 的工作线程
我试图让我的 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 可以解决您的问题。但我建议重写您的代码并使用基于任务的异步操作来完成您的长 运行 初始化任务。
你到底想用定时器解决什么问题?
在我的应用程序中,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.
这是我正在使用的线程:
- 线程 A 是 WPF UI 线程
- 线程 B 是用于创建视图模型(和 ObservableCollection)的工作线程
- 线程 C 是另一个将项目添加到 ObservableCollection 的工作线程
我试图让我的 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 可以解决您的问题。但我建议重写您的代码并使用基于任务的异步操作来完成您的长 运行 初始化任务。 你到底想用定时器解决什么问题?