遍历不断修改的List

时间:2018-11-29 00:06:06

标签: c# wpf list user-interface

因此,我正在从arduino中读取恒定的串行数据流,以验证程序中的某些内容。但是显示这些会锁定UI线程。因此,我的“解决方案”是创建一个保留串行数据的缓冲区,然后使用计时器以固定间隔而不是恒定流的形式将数据放置在UI线程上。

我的代码:

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.'。我知道为什么会发生,但是我不知道如何正确地做到这一点。而且也不知道该用什么Google。

4 个答案:

答案 0 :(得分:4)

您可以采取以下两种方法来防止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());
            }
        }
    }
    

答案 1 :(得分:1)

最简单的解决方案是通过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());
        }
    }
}

答案 2 :(得分:1)

问题在于,当您使用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

答案 3 :(得分:0)

响应式扩展提供了大多数现成的功能。

签出Intro to RXObservable TimerReplacing EventsReactiveUI