Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/335.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何从列表中删除项目<;T>;在使用多个线程枚举列表时?_C#_Enumeration - Fatal编程技术网

C# 如何从列表中删除项目<;T>;在使用多个线程枚举列表时?

C# 如何从列表中删除项目<;T>;在使用多个线程枚举列表时?,c#,enumeration,C#,Enumeration,我有一个奇怪的场景,其中我必须在多个线程上枚举一个列表,并且在这些线程上执行的方法必须能够从列表中删除项。是的,我理解所反映的设计问题,但这正是我需要做的 我无法在执行时删除项目,因为这会导致此异常: 收集被修改;枚举操作不能执行 如果这是单线程的,我会这样解决问题: for (var i = list.Count - 1; i >= 0; i--) { // depending on some condition: list.RemoveAt(i); } public

我有一个奇怪的场景,其中我必须在多个线程上枚举一个列表,并且在这些线程上执行的方法必须能够从列表中删除项。是的,我理解所反映的设计问题,但这正是我需要做的

我无法在执行时删除项目,因为这会导致此异常:

收集被修改;枚举操作不能执行

如果这是单线程的,我会这样解决问题:

for (var i = list.Count - 1; i >= 0; i--)
{
    // depending on some condition:
    list.RemoveAt(i);
}
public class Deletable<T>
{
    public Deletable(T value)
    {
        Value = value;
    }

    public T Value { get; }

    public bool Delete { get; private set; }

    public void MarkForDeletion() => Delete = true;
}

当多个线程正在枚举列表时,如何解决此问题?

如上所述,最好用其他方法解决此问题,例如将列表拆分为单独的列表。但对于那个奇怪的一次性场景:

不要在列表中删除由多个线程处理的项目,而是将它们标记为删除。然后,当所有方法处理完列表后,从未标记为删除的项中创建一个新列表

在不向现有类添加属性的情况下实现此目的的一种方法是创建一个新类,并将现有类的实例包装到新类中,如下所示:

for (var i = list.Count - 1; i >= 0; i--)
{
    // depending on some condition:
    list.RemoveAt(i);
}
public class Deletable<T>
{
    public Deletable(T value)
    {
        Value = value;
    }

    public T Value { get; }

    public bool Delete { get; private set; }

    public void MarkForDeletion() => Delete = true;
}
结果是一个
Deletable[]
,您可以将其传递给其他方法。它甚至不是一个列表,所以这些方法无法从中删除项目。相反,他们会打电话

item.MarkForDeletion();
当所有线程都完成处理后,您将通过调用以下命令为未删除的项目创建一个新的筛选结果:

var filtered = deletables.FilterDeleted();


这允许不同的线程出于不同的原因标记要删除的项目。换句话说,他们做的事情并不完全相同。如果每个线程执行完全相同的检查,则应使用
Parallel.ForEach
或将列表拆分为较小的列表来处理。我无法想象多个线程在同一个列表中执行相同操作的场景。

最可靠的方法可能是使用为从多个线程操作而构建的集合类型。名称空间中有几个用于此目的的类。虽然这些类支持从多个线程添加项,但大多数类不允许从集合中删除特定项;相反,您只能
TryTake
下一个可用项目

但是,该类确实为您提供了删除特定项的能力,因为每个项都有一个唯一的
键来标识该项

如果可以将集合移动到字典中(添加集合时只需为集合中的每个项指定一个唯一的键),那么下面是一个示例,说明如何执行此操作,每个线程在整个集合中迭代5个不同的线程,每个线程根据不同的(但在许多情况下是相互竞争的)条件删除项:

private static ConcurrentDictionary<int, int> dict = new ConcurrentDictionary<int, int>();

private static void RemoveItems(Func<int, bool> condition)
{
    int temp;

    foreach (var item in dict)
    {
        if (condition.Invoke(item.Value)) dict.TryRemove(item.Key, out temp);
    }
}

private static void Main()
{
    Random r = new Random();

    // Start with a list of random integers ranging in value from 1 to 100
    var list = Enumerable.Range(0, 100).Select(x => r.Next(1, 101)).ToList();

    // Add our items to a concurrent dictionary
    for (int i = 0; i < list.Count; i++) dict.TryAdd(i, list[i]);

    // Start 5 tasks where each one removes items based on a
    // different (yet overlapping in many cases) condition
    var tasks = new[]
    {
        Task.Factory.StartNew(() => RemoveItems(i => i % 15 == 0)),
        Task.Factory.StartNew(() => RemoveItems(i => i % 10 == 0)),
        Task.Factory.StartNew(() => RemoveItems(i => i % 5 == 0)),
        Task.Factory.StartNew(() => RemoveItems(i => i % 3 == 0)),
        Task.Factory.StartNew(() => RemoveItems(i => i % 2 == 0)),
    };

    // Wait for tasks
    Task.WaitAll(tasks);

    // Reassign our list to the remaining values
    list = dict.Values.ToList();

    GetKeyFromUser("\nDone! Press any key to exit...");
}
私有静态ConcurrentDictionary dict=new ConcurrentDictionary();
私有静态无效删除项(Func条件)
{
内部温度;
foreach(dict中的var项目)
{
if(condition.Invoke(item.Value))dict.TryRemove(item.Key,out temp);
}
}
私有静态void Main()
{
随机r=新随机();
//从1到100的随机整数列表开始
var list=Enumerable.Range(01100)。选择(x=>r.Next(1101)).ToList();
//将我们的项目添加到并发字典
对于(inti=0;iremovietems(i=>i%15==0)),
Task.Factory.StartNew(()=>removietems(i=>i%10==0)),
Task.Factory.StartNew(()=>removietems(i=>i%5==0)),
Task.Factory.StartNew(()=>removietems(i=>i%3==0)),
Task.Factory.StartNew(()=>removietems(i=>i%2==0)),
};
//等待任务
Task.WaitAll(任务);
//将我们的列表重新分配给剩余的值
list=dict.Values.ToList();
GetKeyFromUser(“\n完成!按任意键退出…”);
}

这里尝试通过使枚举器可由多个线程同时使用,并允许在枚举期间从列表中删除元素,来直接解决此问题。列表中的每个元素只枚举一次,因此每个线程只处理列表的一个子集。我认为这项安排更符合问题的要求

我无法使用标准枚举器,因为从调用
MoveNext
到调用
Current
持有锁的风险太大,因此枚举器是非标准的。它有一个方法
MoveNext
,该方法还将当前元素作为
out
参数返回。实际上,它返回当前元素的包装,其中包括一个
Remove()
方法,可以调用该方法从列表中删除元素

public class ThreadSafeEnumerator<T>
{
    private readonly IList<T> _list;
    private readonly List<int> _indexes;
    private readonly object _locker = new object();
    private int _currentIndex;

    public ThreadSafeEnumerator(IList<T> list)
    {
        _list = list;
        _indexes = Enumerable.Range(0, list.Count).ToList();
        _currentIndex = list.Count;
    }

    public bool MoveNext(out Removable current)
    {
        current = default;
        T item;
        int i;
        lock (_locker)
        {
            _currentIndex--;
            i = _currentIndex;
            if (i < 0) return false;
            item = _list[i];
        }
        current = new Removable(item, () =>
        {
            lock (_locker)
            {
                var index = _indexes.BinarySearch(i);
                _indexes.RemoveAt(index);
                _list.RemoveAt(index);
            }
        });
        return true;
    }

    public struct Removable
    {
        public T Value { get; }
        private readonly Action _action;

        public Removable(T value, Action action)
        {
            Value = value;
            _action = action;
        }

        public void Remove() => _action();
    }
}
公共类ThreadSafeEnumerator
{
私有只读IList_列表;
私有只读列表索引;
私有只读对象_locker=新对象();
私有int_currentIndex;
公共线程安全枚举器(IList列表)
{
_列表=列表;
_index=Enumerable.Range(0,list.Count).ToList();
_currentIndex=list.Count;
}
公共bool MoveNext(输出可移动电流)
{
当前=默认值;
T项;
int i;
锁(储物柜)
{
_当前索引--;
i=_currentIndex;
如果(i<0)返回false;
项目=_列表[i];
}
当前=新的可移动(项目,()=>
{
锁(储物柜)
{
var index=_index.BinarySearch(i);
_索引。RemoveAt(索引);
_列表。删除(索引);
}
});
返回true;
}
公共结构可移动
{
公共价值
Random random = new Random(0);
var list = Enumerable.Range(0, 10000).Select(_ => random.Next(0, 10000)).ToList();
var enumerator = new ThreadSafeEnumerator<int>(list);
var tasks = Enumerable.Range(0, 4).Select(_ => Task.Run(() =>
{
    while (enumerator.MoveNext(out var current))
    {
        if (current.Value % 2 != 0) current.Remove();
    }
})).ToArray();
Task.WaitAll(tasks);
Console.WriteLine($"Count: {list.Count}");
Console.WriteLine($"Top Ten: {String.Join(", ", list.OrderBy(n => n).Take(10))}");