在新线程上使用ObservableCollection

时间:2012-11-05 10:17:58

标签: c# wpf multithreading dispatcher

几天前我创建了this thread,因为我无法从另一个线程更新ObservableCollection。 这是线程的解决方案:

Dispatcher.CurrentDispatcher.BeginInvoke(new Action(delegate
{
    TheTVDB theTvdb = new TheTVDB();
    foreach (TVSeries tvSeries in theTvdb.SearchSeries("Dexter"))
    {
        this.Overview.Add(tvSeries);
    }
}),
DispatcherPriority.Background);

然而,似乎这不是真正的解决方案,因为UI在执行委托时仍然会冻结。我的猜测是,上面并没有在另一个线程上运行任何东西,而是将它全部发送到UI线程。 所以我真正想做的是自己创建一个新线程并进行加载(这发生在theTvdb.SearchSeries())。然后我将遍历结果并将其添加到我的ObservableCollection,这必须发生在UI线程上。

这种方法听起来合适吗?

我提出了下面的内容,我认为会加载结果并将其添加到ObervableCollection并在我的列表视图中显示它们而不会冻结UI。

Thread thread = new Thread(new ThreadStart(delegate
{
    TheTVDB theTvdb = new TheTVDB();
    List<TVSeries> dexter = theTvdb.SearchSeries("Dexter");

    foreach (TVSeries tvSeries in dexter)
    {
        Dispatcher.CurrentDispatcher.BeginInvoke(new Action(delegate
        {
            this.Overview.Add(tvSeries);
        }),
        DispatcherPriority.Normal);
    }
}));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();

以上不会产生任何错误。相反没有任何反应用户界面不会冻结,但不会更新。 Overview中的对象未在UI中显示,我已经测试了绑定是否正确。如果我不加载它们并将它们添加到另一个线程上的ObservableCollection,对象将正确显示。

我尝试过的另一个解决方案是将MTObservableCollection from this answer用于类似的问题。当使用ObservableCollection的子类时,我自己没有发送任何内容。这给了我以下错误:

  

必须在与DependencyObject相同的线程上创建DependencySource。

任何人都可以告诉我如何:

  1. 在单独的线程上加载内容
  2. 使用步骤1中的结果更新绑定到listview的ObservableCollection
  3. 在没有UI冻结的情况下在UI中显示结果
  4. 我希望你能进一步帮助我。

    更新

    <UserControl
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:acb="clr-namespace:AttachedCommandBehavior"
        mc:Ignorable="d"
        x:Class="TVSeriesLibrary.OverviewView"
        x:Name="UserControl"
        d:DesignWidth="512"
        d:DesignHeight="480">
    
        <UserControl.Resources>
            <DataTemplate x:Key="CoverTemplate">
                <StackPanel Orientation="Horizontal">
                    <Image Width="82" Height="85" Stretch="Fill" Source="{Binding Cover}" Margin="10,10,0,10"/>
                </StackPanel>
            </DataTemplate>
        </UserControl.Resources>
    
        <Grid x:Name="LayoutRoot" Background="#515050">
            <Grid.Resources>
                <ResourceDictionary>
                    <Style x:Key="ItemContStyle" TargetType="{x:Type ListViewItem}">
                        <Setter Property="Background" Value="#282828" />
                        <Setter Property="Margin" Value="0,0,0,5" />
                        <Setter Property="Padding" Value="0" />
                    </Style>
                </ResourceDictionary>
            </Grid.Resources>
    
            <ListView Height="112"
                      Width="488"
                      Margin="12,150,12,218"
                      Foreground="#ffffff"
                      Background="#515050"
                      VerticalContentAlignment="Center"
                      BorderThickness="0"
                      ItemTemplate="{StaticResource CoverTemplate}"
                      ItemsSource="{Binding Overview}">
                <ListView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel Orientation="Horizontal" />
                    </ItemsPanelTemplate>
                </ListView.ItemsPanel>
            </ListView>
    
            <ListView Height="170"
                      Margin="10,298,10,0"
                      VerticalAlignment="Center"
                      Foreground="#ffffff"
                      Background="#515050"
                      VerticalContentAlignment="Center"
                      BorderThickness="0"
                      Width="488" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                      ItemsSource="{Binding Path=Overview}"
                      SelectedItem="{Binding Path=SelectedTVSeries}"
                      ItemContainerStyle="{StaticResource ItemContStyle}">
                <ListView.Resources>
                    <ResourceDictionary>
                        <Style x:Key="hiddenStyle" TargetType="GridViewColumnHeader">
                            <Setter Property="Visibility" Value="Collapsed"/>
                        </Style>
                    </ResourceDictionary>
                </ListView.Resources>
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Cover" Width="auto" HeaderContainerStyle="{StaticResource hiddenStyle}">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <Image Source="{Binding Path=Cover}" Height="50" Margin="-6,0,0,0" />
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
    
    
                        <GridViewColumn Header="Title" Width="200" HeaderContainerStyle="{StaticResource hiddenStyle}">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding Path=Name}" FontWeight="Bold"></TextBlock>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
    
                        <GridViewColumn Header="Year" Width="100" HeaderContainerStyle="{StaticResource hiddenStyle}">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding Path=DisplayYear}"></TextBlock>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
    
                        <GridViewColumn Header="Button" Width="135" HeaderContainerStyle="{StaticResource hiddenStyle}">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <Button Content="Details" Width="100" Height="20" />
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
    
                    </GridView>
                </ListView.View>
            </ListView>
        </Grid>
    
    </UserControl>
    

3 个答案:

答案 0 :(得分:1)

在您想要保持响应的应用程序中的任何“繁重”工作中进行多线程的方法是正确的思考方式,因此您处于正确的轨道上。

但是,在这里创建和使用其他线程时,您仍然过多地依赖Dispatcher。请注意,在这里使用多线程,您的流程应如下所示:

  1. 在单独的线程上进行繁重的工作。
  2. 完成后,请Dispatcher根据需要更新UI。
  3. 这最大限度地减轻了Dispatcher的负担。

    你考虑过使用任务吗?从“干净代码”的角度来看它们很棒,但在这里适用,因为通过Task Continuation,您可以将任务链接在一起,以便在UI 上完成繁重的工作后调用UI上的相关代码线程。

    看看answer here是一个好的开始。

    如果您在此之后需要,我会很乐意提供更详细的示例。

    编辑:正如另一个答案中所提到的,BackgroundWorker在这里同样有效......最终结果与线程角度完全相同。我只喜欢Task语法!

    编辑:我以为我会提供一些代码。我暂时不会为了简单而继续。考虑以下方法来解决这个问题:

        public void HeavyLifting(Action<List<Items>> callback)
        {
            Task<List<Items>> task = Task.Factory.StartNew(
                () =>
                    {
                        var myResults = new List<Items>();
    
                        // do the heavy stuff.
    
                        return myResults;
                    });
    
            callback.Invoke(task.Result);
        }
    

    然后,对于您的UI(例如,在ViewModel中),您可以调用和处理回调。如果需要,请拨打“举重”并传递回调:

    HeavyLifting(this.HandleHeavyLiftingCompleted);
    

    然后,您在传递任务完成时执行回调时传递的方法。请注意,这是我要求Dispatcher完成工作的地方:

    private void HandleHeavyLiftingCompleted(List<Items> results)
    {
        this._uiDispatcher.BeginInvoke(
            new Action(() => { this.MyItems = new ObservableCollection<Items>(results); }));
    }
    

    请注意,在这种情况下,涉及的UI工作是更新我从View绑定到的ObvservableCollection。对于这里的示例,我使用随机的“Item”对象,可以是您喜欢的任何对象!

    我正在使用Cinch,因此依靠服务来获取相关的Dispatcher(你在这里看到这个._uiDispatcher)。在您的情况下,您可以使用此处其他问题中提到的方法来获取对它的引用。

    此外,如果您有时间阅读,那么有关WPF线程模型的一些重要信息here

答案 1 :(得分:0)

您的方法很危险,在很短的时间内向调度员推送大量工作会导致您的申请停滞或冻结。虽然您的一般方法很好,但您可能需要考虑在列表中使用批量添加元素。

此外,您无法使用Dispatcher.CurrentDispatcher,因为您现在正在使用当前线程的调度程序。因此,您要求您的线程,在同一个线程中处理添加,而不是ui线程。您需要从ui线程获取调度程序。例如,您可以使用Application对象。

我还建议你在我的经验中使用BackgroundWorker它在WPF中比在普通线程中运行得更好。

答案 2 :(得分:0)

你可以这样做:

Task.Factory.StartNew(() => 
{
    var theTvdb = new TheTVDB();
    var dexterSeries = theTvdb.SearchSeries("Dexter");
    Application.Current.Dispatcher.Invoke(new Action(() => 
    {    
        foreach (var tvSeries in dexterSeries)
        {
            this.Overview.Add(tvSeries);
        }
    }));
});