【问题标题】:Fill TreeView with data from a collection asynchronously用集合中的数据异步填充 TreeView
【发布时间】:2021-04-17 21:39:43
【问题描述】:

我正在尝试以 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;
    }
}

【问题讨论】:

  • 我们在谈论什么样的应用程序(WinForms、WPF 等)?
  • 嗨彼得,它是 WinForms。
  • 我认为Dispatcher.CurrentDispatcher 会比使用SynchronizationContext 更好。你可以使用它的InvokeBeginInvoke methods 来调度一个在UI线程上运行的方法。

标签: c# loops async-await thread-safety synchronizationcontext


【解决方案1】:

List&lt;T&gt; 不是线程安全的。当您尝试在 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 对象上运行。

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

【讨论】:

    猜你喜欢
    • 2018-10-16
    • 1970-01-01
    • 2010-09-26
    • 1970-01-01
    • 1970-01-01
    • 2019-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多