C# 异步使用集合中的数据填充TreeView

C# 异步使用集合中的数据填充TreeView,c#,loops,async-await,thread-safety,synchronizationcontext,C#,Loops,Async Await,Thread Safety,Synchronizationcontext,我试图以50毫秒的时间间隔将一批节点异步添加到TreeView中,但我得到一个异常,即集合被修改 Collection was modified; enumeration operation may not execute. 我怎样才能防止呢 public partial class Form1 : Form { private SynchronizationContext synchronizationContext; private DateTime previousTi

我试图以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;
    }
}
公共部分类表单1:表单
{
私有同步上下文同步上下文;
private DateTime previousTime=DateTime.Now;
私有列表_batch=新列表();
公共表格1()
{
初始化组件();
}
私有异步无效按钮1\u单击(对象发送方,事件参数e)
{
synchronizationContext=synchronizationContext.Current;
等待任务。运行(()=>
{
for(var i=0;i
列表
不是线程安全的。您试图在
foreach
中读取列表时修改列表

有几种方法可以解决这个问题

使用ConcurrentQueue 更改:

private List<int> _batch = new List<int>();
然后,您可以将它们全部取出,并以线程安全的方式添加它们:

while(_batch.TryDequeue(out var n))
{
    // ...
}
使用
列表
,但与线程同步对象一起使用 创建一个新对象,如下所示:

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();
        }
    }
}
这确保一次只有一个线程在
列表
对象上运行


还有其他方法可以做到这一点,但这是问题的要点。由于同步数据的开销,您的代码不会那么快,但您不会崩溃。

我们讨论的是什么类型的应用程序(WinForms、WPF等)?嗨,Peter,这是WinForms。我认为
Dispatcher.CurrentDispatcher
将是比使用
SynchronizationContext
更好的选择。您可以使用它的
Invoke
BeginInvoke
来调度一个在UI线程上运行的方法。
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();
        }
    }
}