用集合中的数据异步填充 TreeView
Fill TreeView with data from a collection asynchronously
我试图以 50 毫秒的时间间隔将一批节点异步添加到 TreeView,但我收到一个异常,表明集合已被修改。
Collection was modified; enumeration operation may not execute.
我该如何预防?
public partial class Form1 : Form
{
private SynchronizationContext synchronizationContext;
private DateTime previousTime = DateTime.Now;
private List<int> _batch = new List<int>();
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
synchronizationContext = SynchronizationContext.Current;
await Task.Run(() =>
{
for (var i = 0; i <= 5000000; i++)
{
_batch.Add(i);
UpdateUI(i);
}
});
}
public void UpdateUI(int value)
{
var timeNow = DateTime.Now;
if ((DateTime.Now - previousTime).Milliseconds <= 50) return;
synchronizationContext.Post(new SendOrPostCallback(o =>
{
foreach (var item in _batch)
{
TreeNode newNode = new TreeNode($"{item}");
treeView1.Nodes.Add(newNode);
}
}), null);
_batch.Clear();
previousTime = timeNow;
}
}
List<T>
不是线程安全的。您试图在 foreach
.
中读取列表时试图修改列表
有几种方法可以解决这个问题。
使用并发队列
变化:
private List<int> _batch = new List<int>();
收件人:
var _batch = new ConcurrentQueue<int>();
您可以通过调用添加项目:
_batch.Enqueue(1);
然后你可以把它们全部抓出来,并以线程安全的方式添加它们:
while(_batch.TryDequeue(out var n))
{
// ...
}
使用 List
,但使用线程同步对象
像这样创建一个新对象:
private readonly _listLock = new object();
然后添加这些辅助方法:
private void AddToBatch(int value)
{
lock(_listLock)
{
_batch.Add(value);
}
}
private int[] GetAllItemsAndClear()
{
lock(_listLock)
{
try
{
return _batch.ToArray();
}
finally
{
_batch.Clear();
}
}
}
这确保一次只有一个线程在 List
对象上运行。
还有其他方法可以做到这一点,但这是问题的要点。由于同步数据的开销,您的代码不会那么快,但您不会崩溃。
我试图以 50 毫秒的时间间隔将一批节点异步添加到 TreeView,但我收到一个异常,表明集合已被修改。
Collection was modified; enumeration operation may not execute.
我该如何预防?
public partial class Form1 : Form
{
private SynchronizationContext synchronizationContext;
private DateTime previousTime = DateTime.Now;
private List<int> _batch = new List<int>();
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
synchronizationContext = SynchronizationContext.Current;
await Task.Run(() =>
{
for (var i = 0; i <= 5000000; i++)
{
_batch.Add(i);
UpdateUI(i);
}
});
}
public void UpdateUI(int value)
{
var timeNow = DateTime.Now;
if ((DateTime.Now - previousTime).Milliseconds <= 50) return;
synchronizationContext.Post(new SendOrPostCallback(o =>
{
foreach (var item in _batch)
{
TreeNode newNode = new TreeNode($"{item}");
treeView1.Nodes.Add(newNode);
}
}), null);
_batch.Clear();
previousTime = timeNow;
}
}
List<T>
不是线程安全的。您试图在 foreach
.
有几种方法可以解决这个问题。
使用并发队列
变化:
private List<int> _batch = new List<int>();
收件人:
var _batch = new ConcurrentQueue<int>();
您可以通过调用添加项目:
_batch.Enqueue(1);
然后你可以把它们全部抓出来,并以线程安全的方式添加它们:
while(_batch.TryDequeue(out var n))
{
// ...
}
使用 List
,但使用线程同步对象
像这样创建一个新对象:
private readonly _listLock = new object();
然后添加这些辅助方法:
private void AddToBatch(int value)
{
lock(_listLock)
{
_batch.Add(value);
}
}
private int[] GetAllItemsAndClear()
{
lock(_listLock)
{
try
{
return _batch.ToArray();
}
finally
{
_batch.Clear();
}
}
}
这确保一次只有一个线程在 List
对象上运行。
还有其他方法可以做到这一点,但这是问题的要点。由于同步数据的开销,您的代码不会那么快,但您不会崩溃。