我有一个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();
});
}
}
}
虽然这似乎有效,但经过一些更新后,用户界面锁定并且更新速度非常慢/如果没有。对于测试,它只会在计时器被触发时在列表中获得几百个项目。
任何人都可以告诉我为什么会发生这种情况 - 我假设我的设计很差。
由于
答案 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的语法是否正确。