异步地向ObservableCollection(或其替代)添加项目

10
这是我的内容: - 一个ListBox,ItemsSource设置为ObservableCollection - T是自定义类,表示文件,包含两个依赖属性:文件名和缩略图路径。 - ListBox还有一个自定义DataTemplate,用于在下面漂亮地显示图像和文件名。 ListBox的目的是在当前文件夹中(在TreeView中选择)显示视频文件,并显示缩略图(异步生成;不是此问题的一部分)。
所以当我更改TreeView中的文件夹时,ObservableCollection被清除并重新填充,这会自动反映在ListBox项中。
问题是:UI变得无响应,更新需要几秒钟。再次说明,缩略图在这里并不重要(我尝试禁用它们)。 我认为最耗时的是构建50-100个自定义类的实例及其可视化表示——必须为每个对象初始化Image对象。但这只是我的猜测——请问您能否确认或排除这种可能性?
我开始觉得ObservableCollection在这里可能不是正确的选择,因为根据我所读到的和所尝试的,似乎没有办法异步添加项目,至少对于这些项目是DependencyObjects的情况而言。我尝试使用BackgroundWorker创建我的类实例,并将它们添加到集合中的ProgressChanged事件处理程序中,但它会抛出异常(某些线程与依赖关系对象的问题)。
我是否遗漏了什么?或者我最好放弃ObservableCollection,编写一个好的旧异步for循环来添加项目?

1
确切的异常类型和消息是什么? - Adam
"必须在与 DependencyObject 相同的线程上创建 DependencySource" - oli.G
Binding, IsAsync = True 是什么意思? - Lei Yang
3个回答

20

由于您的ObservableCollection绑定到UI上,因此它在UI线程上生成,因此任何进一步的更新(删除/添加/清除)都必须在同一UI线程上进行。它不允许来自另一个线程的更新。

但是,您可以这样做:创建一个类的实例(或在后台线程上执行所有耗时操作),完成后使用您的UI线程的Dispatcher将对象添加到ObservableCollection中,如下所示-

App.Current.Dispatcher.BeginInvoke((Action)delegate()
                          {
                              observableCollection.Add(instanceOfYourClass);
                          });

Dispatcher 的作用是将操作放在其关联的线程上。因此,该项始终会添加到 UI 线程,但可以在后台线程中创建。

以下是一些可能对您有帮助的链接 - 从 BW 更新 和另一个链接是 这里


谢谢,我会尝试这个方法,并且一定会查看这篇有趣的文章。不过我之前也尝试过类似的东西(现在找不到了),但是没有成功……我想它可能与我的类是 DependencyObject 有关,类似于“你不能在一个线程中创建DependencyObject并在另一个线程中使用它”的问题。 - oli.G
是的,你的 DP 必须在 UI 线程上创建。如果你想将其绑定到你的 UI 上,你必须使用 DP,但如果不需要,我建议使用简单的 POCO 属性实现 INPC 接口。 - Rohit Vats
我确实想要绑定它... 我需要Listbox显示我的FILE类实例列表,并应用我的自定义数据模板。我想我会选择INPC。 - oli.G

11

使用 .net 4.5,您可以使用 EnableCollectionSynchronization。

 object lockObj = new object();
        BindingOperations.EnableCollectionSynchronization(yourObservableCollection, lockObj);

2

是的,我知道这是一个老问题,但是像这样的问题无处不在。在IAsynEnumerable出现之前,我发现这是可能的,但是现在有了IAsyncEnumerable,它甚至更容易了。

ViewModel类中的属性

public ObservableCollection<Card> Cards { get; set; }

ViewModel构造函数

public CardViewModel(IDbContextFactory<CardContext> ccf)
{
    this.ctxFactory = ccf; //DB Context, using DI
    this.Cards = new ObservableCollection<Card>();

    Task.Run(async () =>
    {
        await foreach (Card card in GetAllCards())
        {
            this.Cards.Add(card);
        }
    });
}

private IAsyncEnumerable<Card> GetAllCards()
{
    CardContext cc = this.ctxFactory.CreateDbContext();
            
    return cc.Cards
        .Include(cc => cc.Edition)
        .Include(cc => cc.Site)
        .Include(cc => cc.Condition)
        .Include(cc => cc.Purchases)
        .Include(cc => cc.Trades)
        .AsNoTracking()
        .AsAsyncEnumerable();
}

卡片(Cards)绑定在视图(View)中的数据网格(Datagrid)上。
ItemsSource="{Binding Cards}"

'

卡片'逐一添加到网格中,我可以在加载过程中与网格进行交互,而应用程序不会冻结。

数据来自使用EF Core 5的数据库,这就是在此示例中CardContext所代表的内容。

'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接