C# 迭代不断修改的列表

C# 迭代不断修改的列表,c#,wpf,list,user-interface,C#,Wpf,List,User Interface,所以我从我的arduino读取一个恒定的串行数据流来验证我程序中的一些东西。但是,显示这些将锁定UI线程。因此,我的“解决方案”是创建一个缓冲区来保存串行数据,然后使用计时器将数据以一定的间隔而不是恒定的流放到UI线程上 我的代码: public partial class ConsoleWindow : Window { private SerialPort _serialPort; private List<string> bufferStrings = new

所以我从我的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();
        });
    }
}
公共部分类控制台窗口:窗口
{
专用串行端口_SerialPort;
私有列表缓冲字符串=新列表();
私有只读分派器计时器=新分派器();
公用控制台窗口(参考SerialPort SerialPort)
{
初始化组件();
如果(serialPort!=null)
{
计时器间隔=新的时间间隔(0,0,0,80);
timer.Tick+=PopQueue;
_serialPort=serialPort;
_serialPort.DataReceived+=DataReceived;
timer.Start();
}
}
私有void PopQueue(对象发送方,事件参数)
{
var queue=bufferStrings;
foreach(队列中的var queueString)
{
追加文本(队列字符串);
}
bufferStrings.Clear();
}
接收到私有无效数据(对象发送方,SerialDataReceivedEventArgs e)
{
如果(_serialPort!=null)
{
Add(((SerialPort)sender.ReadLine());
//AppendText(((SerialPort)sender.ReadLine());
}
}
公共文本(字符串文本)
{
Application.Current.Dispatcher.Invoke(()=>
{
如果(Output.Inlines.Count>100)
{
Output.Inlines.Remove(Output.Inlines.FirstInline);
}
Output.Inlines.Add(文本);
ScrollViewer.ScrollToBottom();
});
}
}

问题是我得到了一个异常:
System.InvalidOperationException:'集合被修改;枚举操作可能无法执行。'
。我知道为什么会这样,但我不知道如何才能正确地做到这一点。也不知道谷歌应该做什么。

反应式扩展提供了大部分现成的功能


请检查,,

被动扩展提供了大多数现成的功能


签出,,

最简单的解决方案是使用
监视器通过
结构同步对
缓冲字符串
队列的访问:

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());
        }
    }
}

最简单的解决方案是使用
监视器
通过
结构同步对
缓冲字符串
队列的访问:

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());
        }
    }
}

这里有两种方法可以防止出现
无效操作异常

  • 在遍历缓冲区内容之前,将其复制到新列表中。您可以通过调用
    var queue=bufferStrings.ToList()请注意,您必须使用System.Linq包含
    使用
    ToList()
  • 通过使用
    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());
            }
        }
    }
    

  • 这里有两种方法可以防止出现
    无效操作异常

  • 在遍历缓冲区内容之前,将其复制到新列表中。您可以通过调用
    var queue=bufferStrings.ToList()请注意,您必须使用System.Linq包含
    使用
    ToList()
  • 通过使用
    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());
            }
        }
    }
    

  • 问题是,当您使用
    foreach
    IEnumerable
    上迭代时,集合正在另一个线程中更改

    您需要的是一个可以同时添加和读取的集合

    在文件的顶部添加

    using System.Collections.Concurrent; 
    
    更改此项:

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

    然后,您可以从队列中读取数据,而不必担心是否有其他内容正在写入:

    private void PopQueue(object sender, EventArgs e)
    {
        while (bufferStrings.TryDequeue(out string dequeued))
            AppendText(dequeued);
    }
    
    这只是不断尝试将项目从队列中取出,直到没有其他项目为止<当队列为空时,code>TryDequeue
    返回false。如果在该方法运行时继续添加项,它将继续处理它们


    问题在于,当您使用
    foreach
    IEnumerable
    上迭代时,集合正在另一个线程中更改

    您需要的是一个可以同时添加和读取的集合

    在文件的顶部添加

    using System.Collections.Concurrent; 
    
    更改此项:

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

    然后,您可以从队列中读取数据,而不必担心是否有其他内容正在写入:

    private void PopQueue(object sender, EventArgs e)
    {
        while (bufferStrings.TryDequeue(out string dequeued))
            AppendText(dequeued);
    }
    
    这只是不断尝试将项目从队列中取出,直到没有其他项目为止<当队列为空时,code>TryDequeue
    返回false。如果在该方法运行时继续添加项,它将继续处理它们


    @elgonzo我担心,如果数据入队速度快于出队速度,使用并发收集理论上可能会导致PO试图解决的问题。@Pragmateek,是的,你是对的。如果Arduino以足够快的速度推送数据足够长的时间,程序可能会陷入PopQueue方法中。哎呀!可能有一些方法可以解决这个问题(比如每次调用PopQueue时只弹出最大数量的元素),但是相比之下,
    lock
    更容易实现……没错。另一种方法可能是在最长时间(如100ms)内弹出。如果您放入的项目太多,取出它们的时间太长,则
    锁定将解决一个问题,同时创建另一个问题。当您取出项目时,添加更多项目的请求将被阻止。如果你真的要放那么多东西,那真是个大问题。这正是您使用
    ConcurrentQueue
    的原因。您需要能够同时放入和取出物品,而不是强迫其中一个停下来等待另一个。@elgonzo我担心使用并发收集理论上可能会导致PO尝试的相同问题