线程和列表<>收藏

发布于 2024-12-11 10:39:15 字数 241 浏览 0 评论 0 原文

我有 List 集合,名为 List;列表

我有两个线程。 一个线程正在枚举所有列表元素并将其添加到集合中。 第二个线程正在枚举所有列表元素并从中删除。

如何才能保证线程安全呢? 我尝试创建全局对象“MyLock”并在每个线程函数中使用 lock(MyLock) 块,但它不起作用。

你能帮助我吗?

I have List<string> collection called List<string> list.

I have two threads.
One thread is enumerating through all list elements and adding to collection.
Second thread is enumerating through all list elements and removing from it.

How can make it thread safe?
I tried creating global Object "MyLock" and using lock(MyLock) block in each thread function but it didn't work.

Can you help me?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

攒一口袋星星 2024-12-18 10:39:15

如果您有权访问 .NET 4.0,则可以使用类 ConcurrentQueue 或一个 BlockingCollection ,其中ConcurrentQueue 支持它。它完全符合您想要做的事情,并且不需要任何锁定。如果列表中没有可用的项目,BlockingCollection 将使您的线程等待。

从 ConcurrentQueue 中删除的示例,您执行类似的操作

ConcurrentQueue<MyClass> cq = new ConcurrentQueue<MyClass>();

void GetStuff()
{
    MyClass item;
    if(cq.TryDeqeue(out item))
    {
        //Work with item
    }
}

,这将尝试删除一个项目,但如果没有可用的项目,则不会执行任何操作。

BlockingCollection<MyClass> bc = BlockingCollection<MyClass>(new ConcurrentQueue<MyClass>());

void GetStuff()
{
    if(!bc.IsCompleated) //check to see if CompleatedAdding() was called and the list is empty.
    {
        try
        {
            MyClass item = bc.Take();
            //Work with item
        }
        catch (InvalidOpperationExecption)
        {
            //Take is marked as completed and is empty so there will be nothing to take
        }
    }
}

这将阻塞并等待Take,直到有可以从列表中获取的内容。完成后,您可以调用CompleteAdding(),当列表变空而不是阻塞时,Take 将抛出一个异常。

If you have access to .NET 4.0 you can use the class ConcurrentQueue or a BlockingCollection with a ConcurrentQueue backing it. It does exactly what you are trying to do and does not require any locking. The BlockingCollection will make your thread wait if there is no items available in the list.

A example of removing from the ConcurrentQueue you do something like

ConcurrentQueue<MyClass> cq = new ConcurrentQueue<MyClass>();

void GetStuff()
{
    MyClass item;
    if(cq.TryDeqeue(out item))
    {
        //Work with item
    }
}

This will try to remove a item, but if there are none available it does nothing.

BlockingCollection<MyClass> bc = BlockingCollection<MyClass>(new ConcurrentQueue<MyClass>());

void GetStuff()
{
    if(!bc.IsCompleated) //check to see if CompleatedAdding() was called and the list is empty.
    {
        try
        {
            MyClass item = bc.Take();
            //Work with item
        }
        catch (InvalidOpperationExecption)
        {
            //Take is marked as completed and is empty so there will be nothing to take
        }
    }
}

This will block and wait on the Take till there is something available to take from the list. Once you are done you can call CompleteAdding() and Take will throw a execption when the list becomes empty instead of blocking.

人生百味 2024-12-18 10:39:15

在不了解更多有关您的计划和要求的情况下,我会说这是一个“坏主意”。在迭代列表内容时更改列表很可能会引发异常。

您最好使用 Queue<> 而不是 List<>,因为 Queue<> 是通过同步设计的记住。

Without knowing more about your program and requirements, I'm going say that this is a "Bad Idea". Altering a List<> while iterating through it's contents will most likely throw an exception.

You're better off using a Queue<> instead of a List<>, as a Queue<> was designed with synchronization in mind.

酒与心事 2024-12-18 10:39:15

您应该能够直接锁定您的列表:

lock(list) {
    //work with list here
}

但是,在枚举列表时添加/删除列表可能会导致异常...

You should be able to lock directly on your list:

lock(list) {
    //work with list here
}

However adding/removing from the list while enumerating it will likely cause an exception...

記柔刀 2024-12-18 10:39:15

锁定 ListSyncRoot

lock(list.SyncRoot)
{

}

有关如何正确使用它的更多信息,请访问 此处

Lock on the SyncRoot of your List<T>:

lock(list.SyncRoot)
{

}

More information on how to use it properly can be found here

迟月 2024-12-18 10:39:15

您可以实现自己的 IList 版本,该版本包装底层 List 以提供对每个方法调用的锁定。

public class LockingList<T> : IList<T>
{
    public LockingList(IList<T> inner)
    {
        this.Inner = inner;
    }

    private readonly object gate = new object();
    public IList<T> Inner { get; private set; }

    public int IndexOf(T item)
    {
        lock (gate)
        {
            return this.Inner.IndexOf(item);
        }
    }

    public void Insert(int index, T item)
    {
        lock (gate)
        {
            this.Inner.Insert(index, item);
        }
    }

    public void RemoveAt(int index)
    {
        lock (gate)
        {
            this.Inner.RemoveAt(index);
        }
    }

    public T this[int index]
    {
        get
        {
            lock (gate)
            {
                return this.Inner[index];
            }
        }
        set
        {
            lock (gate)
            {
                this.Inner[index] = value;
            }
        }
    }

    public void Add(T item)
    {
        lock (gate)
        {
            this.Inner.Add(item);
        }
    }

    public void Clear()
    {
        lock (gate)
        {
            this.Inner.Clear();
        }
    }

    public bool Contains(T item)
    {
        lock (gate)
        {
            return this.Inner.Contains(item);
        }
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        lock (gate)
        {
            this.Inner.CopyTo(array, arrayIndex);
        }
    }

    public int Count
    {
        get
        {
            lock (gate)
            {
                return this.Inner.Count;
            }
        }
    }

    public bool IsReadOnly
    {
        get
        {
            lock (gate)
            {
                return this.Inner.IsReadOnly;
            }
        }
    }

    public bool Remove(T item)
    {
        lock (gate)
        {
            return this.Inner.Remove(item);
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        lock (gate)
        {
            return this.Inner.ToArray().AsEnumerable().GetEnumerator();
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        lock (gate)
        {
            return this.Inner.ToArray().GetEnumerator();
        }
    }
}

您可以像这样使用此代码:

var list = new LockingList<int>(new List<int>());

如果您使用大型列表和/或性能是一个问题,那么这种锁定的性能可能不是很好,但在大多数情况下应该没问题。

请注意,两个 GetEnumerator 方法调用 .ToArray(),这一点非常重要。这会强制在释放锁之前对枚举器进行求值,从而确保对列表的任何修改都不会影响实际的枚举。

使用诸如 lock (list) { ... }lock (list.SyncRoot) { ... } 之类的代码不会列出枚举期间发生的更改。这些解决方案仅涵盖对列表的并发修改 - 并且前提是所有调用者都在锁内执行此操作。此外,如果某些令人讨厌的代码占用了锁并且不释放它,这些解决方案可能会导致您的代码死亡。

在我的解决方案中,您会注意到我有一个对象门,它是我锁定的类内部的私有变量。类之外的任何东西都无法锁定它,因此它是安全的。

我希望这有帮助。

You could implement your own version of IList<T> that wraps the underlying List<T> to provide locking on every method call.

public class LockingList<T> : IList<T>
{
    public LockingList(IList<T> inner)
    {
        this.Inner = inner;
    }

    private readonly object gate = new object();
    public IList<T> Inner { get; private set; }

    public int IndexOf(T item)
    {
        lock (gate)
        {
            return this.Inner.IndexOf(item);
        }
    }

    public void Insert(int index, T item)
    {
        lock (gate)
        {
            this.Inner.Insert(index, item);
        }
    }

    public void RemoveAt(int index)
    {
        lock (gate)
        {
            this.Inner.RemoveAt(index);
        }
    }

    public T this[int index]
    {
        get
        {
            lock (gate)
            {
                return this.Inner[index];
            }
        }
        set
        {
            lock (gate)
            {
                this.Inner[index] = value;
            }
        }
    }

    public void Add(T item)
    {
        lock (gate)
        {
            this.Inner.Add(item);
        }
    }

    public void Clear()
    {
        lock (gate)
        {
            this.Inner.Clear();
        }
    }

    public bool Contains(T item)
    {
        lock (gate)
        {
            return this.Inner.Contains(item);
        }
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        lock (gate)
        {
            this.Inner.CopyTo(array, arrayIndex);
        }
    }

    public int Count
    {
        get
        {
            lock (gate)
            {
                return this.Inner.Count;
            }
        }
    }

    public bool IsReadOnly
    {
        get
        {
            lock (gate)
            {
                return this.Inner.IsReadOnly;
            }
        }
    }

    public bool Remove(T item)
    {
        lock (gate)
        {
            return this.Inner.Remove(item);
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        lock (gate)
        {
            return this.Inner.ToArray().AsEnumerable().GetEnumerator();
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        lock (gate)
        {
            return this.Inner.ToArray().GetEnumerator();
        }
    }
}

You would use this code like this:

var list = new LockingList<int>(new List<int>());

If you're using large lists and/or performance is an issue then this kind of locking may not be terribly performant, but in most cases it should be fine.

It is very important to notice that the two GetEnumerator methods call .ToArray(). This forces the evaluation of the enumerator before the lock is released thus ensuring that any modifications to the list don't affect the actual enumeration.

Using code like lock (list) { ... } or lock (list.SyncRoot) { ... } do not cover you against list changes occurring during enumerations. These solutions only cover against concurrent modifications to the list - and that's only if all callers do so within a lock. Also these solutions can cause your code to die if some nasty bit of code takes a lock and doesn't release it.

In my solution you'll notice I have a object gate that is a private variable internal to the class that I lock on. Nothing outside the class can lock on this so it is safe.

I hope this helps.

谜兔 2024-12-18 10:39:15

正如其他人已经说过的,您可以使用 System.Collections.Concurrent 命名空间中的并发集合。如果您可以使用其中之一,那么这是首选。

但如果您确实想要一个刚刚同步的列表,您可以查看 System.Collections.Generic 中的 SynchronizedCollection 类。

请注意,您必须包含 System.ServiceModel 程序集,这也是我不太喜欢它的原因。但有时我会使用它。

As others already said, you can use concurrent collections from the System.Collections.Concurrent namespace. If you can use one of those, this is preferred.

But if you really want a list which is just synchronized, you could look at the SynchronizedCollection<T>-Class in System.Collections.Generic.

Note that you had to include the System.ServiceModel assembly, which is also the reason why I don't like it so much. But sometimes I use it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文