Why does CollectionViewSource.GetDefaultView(...) return the wrong CurrentItem from inside a Task thread?
c#, multithreading, task, wpf
Solution
I believe `CollectionViewSource.GetDefaultView` is effectively thread-static - in other words, each thread will see a different view. Here's a short test to show that:
using System;
using System.Windows.Data;
using System.Threading.Tasks;
internal class Test
{
static void Main()
{
var source = "test";
var view1 = CollectionViewSource.GetDefaultView(source);
var view2 = CollectionViewSource.GetDefaultView(source);
var view3 = Task.Factory.StartNew
(() => CollectionViewSource.GetDefaultView(source))
.Result;
Console.WriteLine(ReferenceEquals(view1, view2)); // True
Console.WriteLine(ReferenceEquals(view1, view3)); // False
}
}
If you want your task to work on a particular item, I suggest you fetch that item before starting the task.
Problem
I have what I think is a fairly standard setup, a `ListBox` backed by an `ObservableCollection`. I have some work to do with the `Thing`s in the `ObservableCollection` which might take a significant amount of time (more than a few hundred milliseconds) so I'd like to offload that onto a `Task` (I could have also used `BackgroundWorker` here) so as to not freeze the UI. What's strange is that when I do `CollectionViewSource.GetDefaultView(vm.Things).CurrentItem` before starting the `Task`, everything works as expected, however if this happens during the `Task` then `CurrentItem` seems to always point to the first element in the `ObservableCollection`. I've drawn up a complete working example. XAML: ``` <Window x:Class="WpfApplication2.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"> <DockPanel> <ToolBar DockPanel.Dock="Top"> <Button Content="Click Me Sync" Click="ButtonSync_Click" /> <Button Content="Click Me Async Good" Click="ButtonAsyncGood_Click" /> <Button Content="Click Me Async Bad" Click="ButtonAsyncBad_Click" /> </ToolBar> <TextBlock DockPanel.Dock="Bottom" Text="{Binding Path=SelectedThing.Name}" /> <ListBox Name="listBox1" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=Things}" SelectedItem="{Binding Path=SelectedThing}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Path=Name}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </DockPanel> </Window> ``` C#: ``` public partial class MainWindow : Window { private readonly ViewModel vm; public MainWindow() { InitializeComponent(); vm = new ViewModel(); DataContext = vm; } private ICollectionView GetCollectionView() { return CollectionViewSource.GetDefaultView(vm.Things); } private Thing GetSelected() { var view = GetCollectionView(); return view == null ? null : (Thing)view.CurrentItem; } private void NewTask(Action start, Action finish) { Task.Factory .StartNew(start) .ContinueWith(t => finish()); //.ContinueWith(t => finish(), TaskScheduler.Current); //.ContinueWith(t => finish(), TaskScheduler.Default); //.ContinueWith(t => finish(), TaskScheduler.FromCurrentSynchronizationContext()); } private void ButtonSync_Click(object sender, RoutedEventArgs e) { var thing = GetSelected(); DoWork(thing); MessageBox.Show("all done"); } private void ButtonAsyncGood_Click(object sender, RoutedEventArgs e) { var thing = GetSelected(); // outside new task NewTask(() => { DoWork(thing); }, () => { MessageBox.Show("all done"); }); } private void ButtonAsyncBad_Click(object sender, RoutedEventArgs e) { NewTask(() => { var thing = GetSelected(); // inside new task DoWork(thing); // thing will ALWAYS be the first element -- why? }, () => { MessageBox.Show("all done"); }); } private void DoWork(Thing thing) { Thread.Sleep(1000); var msg = thing == null ? "nothing selected" : thing.Name; MessageBox.Show(msg); } } public class ViewModel { public ObservableCollection<Thing> Things { get; set; } public Thing SelectedThing { get; set; } public ViewModel() { Things = new ObservableCollection<Thing>(); Things.Add(new Thing() { Name = "one" }); Things.Add(new Thing() { Name = "two" }); Things.Add(new Thing() { Name = "three" }); Things.Add(new Thing() { Name = "four" }); } } public class Thing { public string Name { get; set; } } ```