通过具有大量数据的可观察集合批量更新UI组件

本文关键字:集合 观察 更新 组件 UI 数据 | 更新日期: 2023-09-27 18:05:55

我有一个WinRT应用程序,每次从设备接收数据时都会触发通知。我还有一个UI控件,它绑定到一个可观察集合,我希望将新数据添加到

虽然我已经使它能够更新可观察集合,但它会导致UI变得非常滞后,因为数据量生成得很快。因此,最好是批量更新,也许每几百毫秒更新一次。

下面显示了代码片段。首先创建周期计时器

 TimerElapsedHandler f = new TimerElapsedHandler(batchUpdate);
        CreatePeriodicTimer(f, new TimeSpan(0, 0, 3));

下面是新数据传入时的事件处理程序,以及存储信息

的临时列表
List<FinancialStuff> lst = new List<FinancialStuff>();
    async void myData_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
    {
        var data = new byte[args.CharacteristicValue.Length];
        DataReader.FromBuffer(args.CharacteristicValue).ReadBytes(data);
        lst.Add(new FinancialStuff() { Time = "DateTime.UtcNow.ToString("mm:ss.ffffff")", Amount = data[0] });
    }

然后是我的批处理更新,称为周期性

    private void batchUpdate(ThreadPoolTimer source)
    {
            AddItem<FinancialStuff>(financialStuffList, lst);                        
    }

最后,为了测试,我想清除可观察对象集合和项。

    public async void AddItem<T>(ObservableCollection<T> oc, List<T> items)
    {
        lock (items)
        {
            if (Dispatcher.HasThreadAccess)
            {
                foreach (T item in items)
                    oc.Add(item);
            }
            else
            {
                Dispatcher.RunAsync(CoreDispatcherPriority.Low, () =>
                {
                    oc.Clear();
                    for (int i = 0; i < items.Count; i++)
                    {
                        items.Count());
                        oc.Add(items[i]);
                    }
                    lst.Clear();
                });
            }
        }
    }

虽然这似乎工作,几个更新后,UI锁定,它更新非常缓慢/如果没有。对于测试,到计时器触发时,它只在列表中获得几百个项目。

谁能告诉我为什么会发生这种情况?我认为我的设计很差。

谢谢

通过具有大量数据的可观察集合批量更新UI组件

您没有在事件处理程序中锁定列表

// "lst" is never locked in your event handler
List<FinancialStuff> lst = new List<FinancialStuff>();
lst.Add(new FinancialStuff() { Time = "DateTime.UtcNow.ToString("mm:ss.ffffff")", Amount = data[0] });

将上面的"lst"传递给async方法

AddItem<FinancialStuff>(financialStuffList, lst);    

你锁定了下面的"items",实际上是上面的"lst"。然而,当你处理它的时候,你正在向列表中添加内容。我假设事件处理程序具有更高的优先级,因此您的处理速度比添加慢。这可能导致"I <物品。永远都是真的。>

public async void AddItem<T>(ObservableCollection<T> oc, List<T> items)
{
    // "lst" reference is locked here, but it wasn't locked in the event handler 
    lock (items)
    {
        if (Dispatcher.HasThreadAccess)
        {
            foreach (T item in items)
                oc.Add(item);
        }
        else
        {
            Dispatcher.RunAsync(CoreDispatcherPriority.Low, () =>
            {
                oc.Clear();
                // This may never exit the for loop
                for (int i = 0; i < items.Count; i++)
                {
                    items.Count());
                    oc.Add(items[i]);
                }
                lst.Clear();
            });
        }
    }
}
编辑:

你需要查看每一条数据吗?使用锁时将会有一些开销。如果你获取数据的速度比你渲染数据的速度快,你最终会被备份和/或有一个非常大的集合来渲染,这也可能会导致一些问题。我建议您做一些过滤,只绘制最后x个项目(比如100个)。而且,我也不确定你为什么需要if (Dispatcher.HasThreadAccess)条件。

尝试以下操作:

public async void AddItem<T>(ObservableCollection<T> oc, List<T> items)
{
    // "lst" reference is locked here, but it wasn't locked in the event handler 
    lock (items)
    {
        // Change this to what you want
        const int maxSize = 100;
        // Make sure it doesn't index out of bounds
        int startIndex = Math.Max(0, items.Count - maxSize);
        int length = items.Count - startIndex;
        List<T> itemsToRender = items.GetRange(startIndex, length);
        // You can clear it here in your background thread.  The references to the objects
        // are now in the itemsToRender list.
        lst.Clear();
        // Dispatcher.RunAsync(CoreDispatcherPriority.Low, () =>
        // Please verify this is the correct syntax
        Dispatcher.Run(() =>
        {
            // At second look, this might need to be locked too
            // EDIT: This probably will just add overhead now that it's not running async.
            // You can probably remove this lock
            lock(oc)
            {
                oc.Clear();
                for (int i = 0; i < itemsToRender.Count; i++)
                {
                    // I didn't notice it before, but why are you checking the count again?
                    // items.Count());
                    oc.Add(itemsToRender[i]);
                }
             }
        });
    }
}

EDIT2: 因为你的AddItem方法已经在后台线程,我不认为你需要运行Dispatcher.RunAsync。相反,我认为最好让它阻塞,这样您就不会多次调用该代码段。尝试使用Dispatcher。而不是运行。我已经更新了上面的代码示例以显示更改。你不应该再需要锁了,因为锁在物品上已经足够好了。另外,验证Dispatcher的语法。