首页 > 解决方案 > 如何从列表中删除项目在使用多个线程枚举列表时?

问题描述

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

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

收藏已修改;枚举操作可能无法执行。

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

for (var i = list.Count - 1; i >= 0; i--)
{
    // depending on some condition:
    list.RemoveAt(i);
}

当多个线程枚举列表时,我该如何解决这个问题?

标签: c#enumeration

解决方案


如前所述,最好以其他方式解决此问题,例如将列表分成单独的列表。但是对于那种奇怪的一次性场景:

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

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

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;
}

然后,您可以使用一些扩展将您的转换List<T>IEnumerable<Deletable<T>>,然后在所有线程完成后过滤掉“已删除”项目:

public static class DeletableExtensions
{
    public static Deletable<T>[] AsDeleteable<T>(this IEnumerable<T> source)
    {
        return source.Select(item => new Deletable<T>(item)).ToArray();
    }

    public static IEnumerable<T> FilterDeleted<T>(this IEnumerable<Deletable<T>> source)
    {
        return source.Where(item => !item.Delete).Select(item => item.Value);
    }
}

不要传递List<Foo>给方法,而是这样做:

var deletables = fooList.AsDeleteable();

结果是Deletable<Foo>[]您可以传递给其他方法的结果。它甚至不是一个列表,因此这些方法无法从中删除项目。相反,他们会打电话给

item.MarkForDeletion();

当您的所有线程都完成处理后,您将创建一个新的、过滤后的未通过调用删除的项目结果:

var filtered = deletables.FilterDeleted();

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


推荐阅读