.ToList()线程安全

cri*_*rip 5 .net c# multithreading thread-safety

我有以下情况:

  • 只有一个thead仅将项目添加到列表中
  • 多个线程可以读取项目。

我知道当我们直接公开列表时,我会在枚举时遇到并发修改。

但是,当我调用.ToList()它时,它成为List的一个实例,并使用.NET Framework的Array.Copy函数复制所有项目。

您的意思是,如果脏读没问题,使用这种方法是否安全?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var container = new Container();
            Thread writer = new Thread(() =>
            {
                for (int i = 0; i < 1000; i++)
                {
                    container.Add(new Item("Item " + i));
                }
            });

            writer.Start();

            Thread[] readerThreads = new Thread[10];
            for (int i = 0; i < readerThreads.Length; i++)
            {
                readerThreads[i] = new Thread(() =>
                {
                    foreach (Item item in container.Items)
                    {
                        Console.WriteLine(item.Name);
                    }
                });
                readerThreads[i].Start();
            }

            writer.Join();

            for (int i = 0; i < readerThreads.Length; i++)
            {
                readerThreads[i].Join();
            }

            Console.ReadLine();
        }
    }

    public class Container
    {
        private readonly IList<Item> _items;

        public Container()
        {
            _items = new List<Item>();
        }

        public IReadOnlyList<Item> Items
        {
            get { return _items.ToList().AsReadOnly(); }
        }

        public void Add(Item item)
        {
            _items.Add(item);
        }
    }

    public class Item
    {
        private readonly string _name;

        public Item(string name)
        {
            _name = name;
        }

        public string Name
        {
            get { return _name; }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Iva*_*oev 7

当脏读正常时使用这种方法是否安全?

简单的回答 - 不,不是。

首先,根据定义,该方法并不能保证这一点。即使您查看当前的实现(您不应该这样做),您也会看到它正在使用List<T>接受IEnumerable<T>. 然后它检查ICollection<T>,如果是,使用CopyTo方法或只是迭代可枚举。两者都是不安全的,因为第一个依赖于方法的(未知)实现,CopyTo而第二个将在(标准行为)集合枚举器在Add. 即使来源是一个List<T>并且正在使用Array您提到的方法http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,d2ac2c19c9cf1d44

Array.Copy(_items, 0, array, arrayIndex, _size);
Run Code Online (Sandbox Code Playgroud)

仍然是不安全的,因为它可能会调用带有_items_size超出的副本_items.Length,当然会抛出异常。只有当此代码以某种方式以原子方式获取两个成员时,您的假设才会正确,但事实并非如此。

所以,简单的不要那样做。

编辑:以上所有内容都适用于您的具体问题。但是我认为对于您所解释的情况有一个简单的解决方案。如果要求单线程添加/多线程读取可接受的陈旧读取,那么可以通过使用包中的ImmutableList<T>类来实现,System.Collections.Immutable如下所示:

public class Container
{
    private ImmutableList<Item> _items = ImmutableList<Item>.Empty;
    public IReadOnlyList<Item> Items { get { return _items; } }
    public void Add(Item item) { _items = _items.Add(item); }
}
Run Code Online (Sandbox Code Playgroud)