Перебирать постоянно изменяемый список - PullRequest
0 голосов
/ 29 ноября 2018

Так что я читаю постоянный поток последовательных данных из моего arduino для проверки некоторых вещей в моей программе.Однако их отображение блокирует поток пользовательского интерфейса.Поэтому мое «решение» - создать буфер, в котором хранятся последовательные данные, а затем использовать таймер для помещения данных в поток пользовательского интерфейса с интервалами вместо постоянного потока.

Мой код:

public partial class ConsoleWindow : Window
{
    private SerialPort _serialPort;
    private List<string> bufferStrings = new List<string>();
    private readonly DispatcherTimer timer = new DispatcherTimer();

    public ConsoleWindow(ref SerialPort serialPort)
    {
        InitializeComponent();
        if (serialPort != null)
        {
            timer.Interval = new TimeSpan(0,0,0,0,80);
            timer.Tick += PopQueue;
            _serialPort = serialPort;
            _serialPort.DataReceived += DataReceived;
            timer.Start();
        }
    }

    private void PopQueue(object sender, EventArgs e)
    {
        var queue = bufferStrings;
        foreach (var queueString in queue)
        {
            AppendText(queueString);
        }
        bufferStrings.Clear();
    }

    private void DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        if (_serialPort != null)
        {
            bufferStrings.Add(((SerialPort)sender).ReadLine());
            //AppendText(((SerialPort) sender).ReadLine());
        }
    }

    public void AppendText(string text)
    {
        Application.Current.Dispatcher.Invoke(() =>
        {
            if (Output.Inlines.Count > 100)
            {
                Output.Inlines.Remove(Output.Inlines.FirstInline);
            }

            Output.Inlines.Add(text);
            ScrollViewer.ScrollToBottom();
        });
    }
}

Проблема в том, что я получаю исключение: System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.'.Я знаю, почему это происходит, но я понятия не имею, как я могу сделать это правильно.И понятия не имею, что гуглить тоже.

Ответы [ 4 ]

0 голосов
/ 29 ноября 2018

Проблема в том, что, пока вы перебираете IEnumerable, используя foreach, коллекция изменяется в другом потоке.

Вам нужна коллекция, которую вы можете одновременно добавлять ичитать из.

В верхней части файла добавить

using System.Collections.Concurrent; 

Изменить это:

private List<string> bufferStrings = new List<string>();

на

private ConcurrentQueue<string> bufferStrings = new ConcurrentQueue<string>();

Изменить

bufferStrings.Add(((SerialPort)sender).ReadLine());

до

bufferStrings.Enqueue(((SerialPort)sender).ReadLine());

Затем вы можете читать из очереди, не беспокоясь о том, что что-то еще записывает в нее:

private void PopQueue(object sender, EventArgs e)
{
    while (bufferStrings.TryDequeue(out string dequeued))
        AppendText(dequeued);
}

Это просто продолжает пытатьсявыносите предметы из очереди, пока их больше нет.TryDequeue возвращает false, когда очередь пуста.Если вы продолжите добавлять элементы во время работы этого метода, он просто продолжит их обрабатывать.

ConcurrentQueue

0 голосов
/ 29 ноября 2018

Самое простое решение - синхронизировать доступ к очереди bufferStrings с использованием конструкции Monitor через lock:

private void PopQueue(object sender, EventArgs e)
{
    lock (bufferStrings)
    {
        foreach (var queueString in bufferStrings)
        {
            AppendText(queueString);
        }
        bufferStrings.Clear();
    }
}

private void DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    if (_serialPort != null)
    {
        lock (bufferStrings)
        {
            bufferStrings.Add(((SerialPort)sender).ReadLine());
            //AppendText(((SerialPort) sender).ReadLine());
        }
    }
}
0 голосов
/ 29 ноября 2018

Вот два решения, которые вы можете предпринять, чтобы предотвратить InvalidOperationException:

  1. Скопировать буфер в новый список перед повторением его содержимого.Вы можете сделать это, вызвав var queue = bufferStrings.ToList(); Обратите внимание, что вы должны включить using System.Linq;, чтобы использовать ToList().
  2. Сделать поток итерации безопасным, окружив его ключевым словом lock:

    private void PopQueue(object sender, EventArgs e)
    {
        lock(bufferStrings)
        {
            foreach (var queueString in bufferStrings)
            {
                AppendText(queueString);
            }
            bufferStrings.Clear();
        }
    }
    
    private void DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        if (_serialPort != null)
        {
            lock(bufferStrings)
            {
                bufferStrings.Add(((SerialPort)sender).ReadLine());
                //AppendText(((SerialPort) sender).ReadLine());
            }
        }
    }
    
0 голосов
/ 29 ноября 2018

Реактивные расширения предоставляют большую часть этой функциональности из коробки.

Извлечение Введение в RX , Наблюдаемый таймер , Замена событий , ReactiveUI

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...