C# 迭代不断修改的列表
所以我从我的arduino读取一个恒定的串行数据流来验证我程序中的一些东西。但是,显示这些将锁定UI线程。因此,我的“解决方案”是创建一个缓冲区来保存串行数据,然后使用计时器将数据以一定的间隔而不是恒定的流放到UI线程上 我的代码: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
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尝试的相同问题