3

数日前、別のスレッドから 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();

上記はエラーを生成しません。代わりに、何も起こりません。UI はフリーズしませんが、更新されません。のオブジェクトがOverviewUI に表示されず、バインドが正しいことをテストしました。ObservableCollectionオブジェクトをロードして別のスレッドに追加しないと、オブジェクトは正しく表示されます。

私が試した別の解決策は、この回答から同様の質問へのMTObservableCollectionを使用することです。のそのサブクラスを使用するときObservableCollection、私は自分で何もディスパッチしませんでした。これにより、次のエラーが発生しました。

DependencyObject と同じ Thread に DependencySource を作成する必要があります。

誰でも私ができる方法を教えてください:

  1. 別のスレッドに何かをロードする
  2. ステップ 1 の結果を使用して、リストビューにバインドされている ObservableCollection を更新します。
  3. UI がフリーズせずに UI に結果が表示されるようにする

あなたが私をさらに助けてくれることを願っています。

更新

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

3 に答える 3

1

応答性を維持したいアプリケーション内の「重い」作業内でのマルチスレッド化のアプローチは、それについて考える正しい方法であるため、正しい方向に進んでいます。

ただし、ここで他のスレッドを作成して操作している間は、依然として Dispatcher に依存しすぎています。ここでマルチスレッドを使用すると、プロセスは次のようになります。

  1. 面倒な作業は別のスレッドで行ってください。
  2. 完了したら、必要に応じて Dispatcher に UI を更新するように依頼します。

これにより、Dispatcher の負荷が軽減されます。

タスクの使用を検討しましたか? これらは「クリーン コード」の観点からは優れていますが、ここで適用できます。なぜなら、タスク コンティニュエーションを使用すると、スレッドで重い作業が完了すると、タスクをチェーンして UI で関連するコードを呼び出すことができるからです。

良いスタートを切るには、こちらの回答をご覧ください。

その後、必要に応じて、より詳細な例を喜んで提供します。

編集:別の回答で述べたように、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 を更新していることに注意してください。ここの例では、好きなようにできるランダムな「アイテム」オブジェクトを使用しています!

私は Cinch を使用しているため、関連する Dispatcher (ここでは this._uiDispatcher として表示されます) を取得するサービスに依存しています。あなたの場合、ここの他の質問に記載されている方法を使用して、それへの参照を取得できます。

また、読む時間があれば、WPF スレッド モデルに関する優れた情報がここにあります。

于 2012-11-05T10:54:13.230 に答える
0

あなたのアプローチは危険です。非常に短い時間でディスパッチャに多くのジョブをプッシュすると、アプリケーションが停止またはフリーズする可能性があります。一般的なアプローチは問題ありませんが、リストに要素を追加するバッチの使用を検討することをお勧めします。

また、現在のスレッドのディスパッチャーを使用しているため、Dispatcher.CurrentDispatcherを使用することはできません。したがって、UIスレッドではなく、同じスレッドでの追加を処理するようにスレッドに要求しています。UIスレッドからディスパッチャーを取得する必要があります。たとえば、Applicationオブジェクトを使用できます。

また、私の経験では、 BackgroundWorkerを使用することをお勧めします。これは、単なるスレッドよりもWPFで少しうまく機能します。

于 2012-11-05T10:39:59.353 に答える
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);
        }
    }));
});
于 2012-11-05T12:46:12.070 に答える