2011-03-30 46 views
2

我有我认为是一个相当标准的设置,ListBox支持ObservableCollection为什么CollectionViewSource.GetDefaultView(...)从任务线程中返回错误的CurrentItem?

我有一些工作做的Thing S IN的ObservableCollection这可能需要很长时间显著量(超过几百毫秒),所以我想卸载该到Task(我也用BackgroundWorker),以免冻结用户界面。

什么奇怪的是,当我开始Task之前做CollectionViewSource.GetDefaultView(vm.Things).CurrentItem,一切正常,但是如果发生这种情况期间Task然后CurrentItem似乎总是指向在ObservableCollection第一要素。

我已经制定了一个完整的工作示例。

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; } 
} 
+0

在任务内启动任务'ReferenceEquals' CollectionViewSource.GetDefaultView(vm.Things)'之前,检查是否有'CollectionViewSource.GetDefaultView(vm.Things)'。 – 2011-03-30 16:22:43

回答

7

我相信CollectionViewSource.GetDefaultView是有效线程静态的 - 换句话说,每个线程将会看到一个不同的看法。这里有一个简短的测试表明:

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 
    }   
} 

如果你想你的任务是在一个特定的项目工作,我建议你在开始任务之前获取该项目。

+2

内部数据绑定引擎是线程静态的,'CollectionViewSource.GetDefaultView'从当前(线程静态)数据绑定引擎请求给定集合的默认视图。 – user7116 2011-03-30 16:54:10

相关问题