几天前我创建了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。
任何人都可以告诉我如何:
我希望你能进一步帮助我。
更新:
<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>
答案 0 :(得分:1)
在您想要保持响应的应用程序中的任何“繁重”工作中进行多线程的方法是正确的思考方式,因此您处于正确的轨道上。
但是,在这里创建和使用其他线程时,您仍然过多地依赖Dispatcher。请注意,在这里使用多线程,您的流程应如下所示:
这最大限度地减轻了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);
}
}));
});