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

时间:2014-05-30 23:29:01

标签: c# user-interface event-handling windows-runtime dispatcher

我有一个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] });

    }

然后我的批量更新,这被称为peroidically

    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();

                });
            }
        }

    }

虽然这似乎有效,但经过一些更新后,用户界面锁定并且更新速度非常慢/如果没有。对于测试,它只会在计时器被触发时在列表中获得几百个项目。

任何人都可以告诉我为什么会发生这种情况 - 我假设我的设计很差。

由于

1 个答案:

答案 0 :(得分:2)

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

// "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”传递给您的异步方法

AddItem<FinancialStuff>(financialStuffList, lst);    

你正在锁定下面的“项目”,这真的是“上面的”。但是,您在处理时会添加到列表中。我假设事件处理程序具有更高的优先级,因此您的处理速度比添加速度慢。这可以导致“i&lt; items.Count”永远为真。

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.Run。我已经更新了上面的代码示例来显示更改。你不应该再需要oc上的锁,因为对物品的锁定已经足够了。另外,验证Dispatcher.Run的语法是否正确。