我可以尝试从BlockingCollection <t>中获取一组项目吗?</t>

时间:2012-09-30 14:35:55

标签: c# multithreading parallel-processing task-parallel-library parallel-extensions

在我正在处理的应用程序中,每秒都会收到数千个更新。立即在UI上反映这些更新是性能上的过度杀伤。

以下代码会产生非常糟糕的性能,因为处理每个更新都需要调用UI线程并向ObservableCollection添加一个新项,然后它会触发CollectionChanged事件。

foreach (var update in updates.GetConsumingEnumerable())
{
    // Handle Update
}

我要做的是让消费者等待一点时间(即50毫秒),让发布者有机会添加更多项目,然后以块的形式处理这些更新。

目前,我正在使用以下代码,但我发现很难预测这些项目在消费之前会保留多长时间,我担心这种方法可能会产生另一个性能瓶颈。

List<Tuple<T, List<string>>> updatesToHandle = new List<Tuple<T, List<string>>>();
while (!updates.IsAddingCompleted)
{
    Tuple<T, List<string>> item = null;
    if (updates.TryTake(out item, 5)) // Try Take for 5 Milliseconds
    {
        updatesToHandle.Add(item);
    }
    else
    {
        var newItems = new List<T>(updatesToHandle.Count);
        var updatedItems = new List<Tuple<T, List<string>>>(updatesToHandle.Count);
        foreach (var update in updatesToHandle)
        {
            try
            {
                // Handle Update
            }
            finally
            {
                updatesToHandle.Clear();
                Thread.Sleep(50);
            }
        }
    }
}

3 个答案:

答案 0 :(得分:3)

我会考虑使用Reactive Extensions ReactiveExtensions。此链接解决了类似的问题Stock Trading Example

答案 1 :(得分:1)

我建议您对代码进行两处更改。

首先,不是让线程休眠,而是创建一个每50毫秒触发一次的计时器。然后,该计时器处理程序将在TryTake上循环,没有延迟,收集当前集合中的所有项目(或最多一些)。如果集合为空,TryTake将立即返回。

其次,每次更新都不要为“更新UI”调用一次。修改更新处理程序,使其接受整个更新列表,而不是一次更新一个更新。这样可以避免等待UI线程所需的时间。

当然,上面假设您修改了更新处理程序,以便它可以一次向ObservableCollection添加多个项目,从而阻止多个CollectionChanged事件。

答案 2 :(得分:0)

我是第二个Rx。具体而言,您需要BufferByTime(如果传入速率高时聚合)或Throttle(如果传入速率太高则丢弃值)。此外,您的UI应该绑定到BehaviorSubject,它将始终缓存最后一个值,以便新订阅者在订阅时立即获得最后一个缓存值。