0

I'm Using a Task to start a while loop to run and constantly collect data from from a USB device. The data can come in very fast, multiple messages per millisecond. I want to display the data in real time using a ListView.

The goal is to have two options to display the data. The first way is to display the newest message at the top of the list view. I've tried calling a dispatcher and inserting the data at the beginning of an ObservableCollection. This works fine with a message every 20 ms or so.

Often the data coming in is the same message again and again with a consistent interval. The second way is to have a row in the listview for each unique message. As a new message comes in it either takes the place of the previous similar message or it is inserted into a new position. I accomplished this by inheriting from ObservableCollection and implementing a binarysearch function to get an index and then replace or insert the newest message. This also worked fine at about the same rate.

The problem is Updating the UI can't keep up with reading the data from the USB device when the traffic coming from the USB device is heavy. It worked fine with low volumes of data but I'm stuck trying to make this thing more efficient.

I've tried creating my own methods in my ExtendedObservableCollection. I created some AddRange methods for each scenario and calling OnCollectionChange after all the updates. The performance this way seems to be even worse than it was before which is very confusing to me. This seems like the way to go. I think the issue has something to do with my while loop which is collecting the data and the AddRange method not getting along.

I also tried calling BindingOperations.EnableCollectionSynchronization(MessageList, balanceLock); with out using the dispatcher and it didn't seem to help much. I put my AddRange methods inside a lock statement.

I also tried running the Batchupdate method in its own while loop running parallel the my loop collecting data it didn't work at all.

This is my loop for reading the messages from the USB device

int interval = 40;

private void BeginReading()
    {
        do
        {
            waitHandle.WaitOne(TimeSpan.FromMilliseconds(.5));

            if (ReadOk)
            {
                MessageBuffer.Add(message);
            }
            if (Stopwatch.ElapsedMilliseconds > interval)
            {
                BatchUpdate();
                MessageBuffer = new List<Message>();
                interval += 40;
            }

        } while (ReceiveReady);

    }

This is one of my AddRange Methods in my extended ObservableCollection

public void AddRangeScroll(List<Message> MessageList)
{
    if (MessageList == null) throw new ArgumentNullException("List");

    foreach (Message message in MessageList)
    {
        Items.Insert(0, message);
    } 

    OnCollectionChanged(newNotifyCollectionChangedEventArgs
(NotifyCollectionChangedAction.Reset));
}

I'm hoping I'll be able to read the data from the USB device and update the ListView in something that resembles real time.

The messages I'm reading are CAN messages and I'm using the PEAK PCANBasic API to connect to one of their gridconnect USB to CAN devices.

  • When reading from USB (Serial Port) always use Asynchronous reads. A blocking synchronous read cannot keep up with the data. Do not use timers. The Async code will work. Windows is multi-processing and another thread in windows may lock your thread from reading the port. – jdweng Aug 02 '19 at 20:32
  • ```Task.Run(() => BeginReading());``` If I'm calling that function this way when the reading is supposed to start aren't I doing what youre saying? I added the timers so the loop doesn't use all the processor resources. I don't think I'm understanding what youre saying completely – kiel dowdle Aug 02 '19 at 20:40
  • The wait one will will block so you aren't using the processor resources. Adding timers will just sloop down the reading and make the issue worse. – jdweng Aug 02 '19 at 23:35
  • You posted very incomplete code. – BionicCode Aug 03 '19 at 07:47
  • What is the use case for this ListView? Is somebody really going to sit and watch it? It might be more useful, and much more fault-tolerant, to output to a log file. Leave viewing the log to a separate program, or use a standard format and a third party tool. That is also much more extensible... think monitoring, telemetry, an IoT app, etc. – John Wu Aug 03 '19 at 09:38

1 Answers1

0

Your approach is absolutely bad. You are blocking threads and keep them busy with useless resources consuming polling. (I don't know why you are making the thread wait. But consider to use a non-blocking wait handle operation like SemaphoreSlim.WaitAsync or similar. Maybe the awaitable Task.Delay is sufficient at this point. No waiting would be the best.)

If you wish to group messages like same type together use a Dictionary<K, V> e.g. Dictionary<string, IEnumerable<string>> where the key is the type of the message and the value a collection of grouped messages. The lookup is very fast and doesn't require any search. For this scenario consider to introduce a type for the key (e.g. an enum) and overwrite GetHash on it to improve performance. If the key is guaranteed to be unique hash tables perform the best.

First option is to use an event driven logic:

public void ReadDataFromUsb()
{
  var timer = new System.Timers.Timer(40);
  timer.Elapsed += ReadNextDataBlockAsync;
}

// Will be invoked every 40 ms by the timer
public async void ReadNextDataBlockAsync(Object source, ElapsedEventArgs e)
{
  await Task.Run(BeginReading);
}

private void BeginReading()
{
  // Read from data source
}

Second option is to use yield return and make BeginRead return an IEnumerable. You can then use it like this:

foreach (var message in BeginRead())
{
  // Process message
}

Third option is to use Producer Consumer Pattern using a BlockingCollection<T>. It exposes a non-blocking TryTake method to get the items that are ready for consumption.

See Dataflow (Task Parallel Library) to get more solutions how to handle data flow without blocking threads.

Since a 40 ms data refresh rate is very high also consider to show snapshots that are expandable when details are needed by the user.

BionicCode
  • 1
  • 4
  • 28
  • 44
  • The event driven logic is working pretty well, thank you. I've been trying to implement the dictionary idea for grouping similar messages but I can't get it to work. I was using this https://stackoverflow.com/a/26676294/9749853 I can't get the values as my items source for my listview – kiel dowdle Aug 08 '19 at 13:58