3

In an application I'm working on, thousands of updates are being received per second. Reflecting these updates on the UI immediately and one by one is performance overkill.

The following code yields very bad performance, because handling each update requires invoking the UI thread and adding a new item to an ObservableCollection which in turn triggers the CollectionChanged event.

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

What I'm trying to do is to make the consumer wait for a little bit of time (i.e. 50 millisecond) to give a chance to the publisher to add more items, and then handle these updates in chunks.

Currently, I'm using the following code, but I find it hard to predict for how long the items will stay in the collection before being consumed, I'm afraid this approach may produce another performance bottleneck.

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);
            }
        }
    }
}
Omar
  • 16,329
  • 10
  • 48
  • 66
  • The issue is in your `// Handle Update` code. please include it. – Scott Chamberlain Sep 30 '12 at 14:41
  • 1
    I really don't like our `finally`. If an exception happens, why would you want to delay it? – svick Sep 30 '12 at 17:07
  • Why don't you increase the granularity of the collection i.e. have a class the encapsulates several updates and have the collection hold objects of this type instead of individual updates. Hence, when you want to remove a bunch of updates you remove only one objects from the queue. – Tudor Sep 30 '12 at 17:20
  • @ScottChamberlain: the updates handler in the first snippet does nothing but invoking the UI thread to add the new item. That triggers vast amount of events and yields bad performance. What I'm trying to do is to add items in bulk using a custom AddRange method – Ahmed Fouad Sep 30 '12 at 22:13
  • @svick: You're correct it's better to move the "Sleep" to be inside the try block. – Ahmed Fouad Sep 30 '12 at 22:13
  • @Tudor: it'll be way harder to bind the ObservableCollection to the UI – Ahmed Fouad Sep 30 '12 at 22:15

3 Answers3

3

I would consider using Reactive Extensions ReactiveExtensions. This link solves a similar problem Stock Trading Example

NeddySpaghetti
  • 13,187
  • 5
  • 32
  • 61
1

I would suggest two changes to your code.

First, rather than having the thread sleep, create a timer that fires every 50 milliseconds. That timer handler will then loop on TryTake with no delay, collecting all of the items that are currently in the collection (or up to some maximum). With no delay, TryTake will return immediately if the collection is empty.

Second, do not call the "update UI" once for each update. Modify the update handler so that it accepts the entire list of updates rather than one update at a time. That will avoid the time involved in waiting for the UI thread.

The above assumes, of course, that you've modified your update handler so that it can add multiple items to the ObservableCollection in one shot, preventing multiple CollectionChanged events.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
0

I second Rx. Specifically you want BufferByTime (if aggregating when incoming rate is high) or Throttle (if throwing away values when incoming rate is too high). Also, your UI should probably be bound to BehaviorSubject, which will always cache the last value so new subscribers get that last cached value immediately when they subscribe.

yzorg
  • 4,224
  • 3
  • 39
  • 57