为什么CollectionViewSource.GetDefaultView(...)从Task线程中返回错误的CurrentItem?

时间:2011-03-30 16:11:27

标签: c# wpf multithreading task

我认为这是一个相当标准的设置,ListBoxObservableCollection支持。

我与Thing中的ObservableCollection做了一些工作,可能需要花费大量时间(超过几百毫秒),所以我想把它卸载到Task(我本可以在这里使用BackgroundWorker)以免冻结用户界面。

奇怪的是,当我在开始CollectionViewSource.GetDefaultView(vm.Things).CurrentItem之前Task时,一切都按预期工作,但如果发生这种情况 期间TaskCurrentItem ObservableCollection 1}}似乎总是指向<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> 中的第一个元素。

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

XAML:

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

C#:

{{1}}

1 个答案:

答案 0 :(得分: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
    }        
}

如果您希望您的任务处理特定项目,我建议您在开始任务之前获取该项目。