我在 C# 中有一个简单类型列表。这个列表可以被多个线程访问:可以添加或删除条目,可以检查条目是否存在。到目前为止,我已将该列表封装在一个只公开这三个操作的对象中。
我有几个案例要处理(和我刚才说的方法不完全一样)。
- 一个线程可以只检查一个条目的存在。 (简单)
- 一个线程可以检查一个条目是否存在,如果不存在,则添加它。
- 一个线程需要检查一个条目是否存在,如果存在,等待直到它被移除。
- 2 和 3 的组合,线程检查条目是否存在,如果它确实存在,它必须等到它被删除,然后才能自己添加它。
整个想法是条目的存在表示锁定。如果条目存在,则无法更改它标识的对象并且代码无法继续,因为它正在别处被修改。
这些可能看起来像是简单的新手情况,但我在并发问题上让自己耳目一新,这让我有点偏执,而且我对 C# 的并发机制也不那么熟悉。
处理此问题的最佳方法是什么?我完全离开了吗?是否应该将检查和添加(测试和设置?)合并为第四个原子操作?我是否会简单地向访问列表的方法添加锁 block ?
另外,是否可以对这种东西进行单元测试(不是简单的操作,并发情况)?
最佳答案
单元测试肯定很难。
这一切都可以通过 .NET 中的“ native ”并发机制合理地简单地完成:锁定语句和 Monitor.Wait
/Monitor.PulseAll
.除非每个项目都有一个单独的监视器,否则您将需要在删除任何内容时唤醒所有线程 - 否则您将无法告诉“正确的”线程唤醒。
如果它真的只是一组 项,您可能需要使用HashSet<T>
而不是 List<T>
顺便说一下,代表系列 - 你提到的与订购无关。
示例代码,假设一组适合您:
using System;
using System.Collections.Generic;
using System.Threading;
public class LockCollection<T>
{
private readonly HashSet<T> items = new HashSet<T>();
private readonly object padlock = new object();
public bool Contains(T item)
{
lock (padlock)
{
return items.Contains(item);
}
}
public bool Add(T item)
{
lock (padlock)
{
// HashSet<T>.Add does what you want already :)
// Note that it will return true if the item
// *was* added (i.e. !Contains(item))
return items.Add(item);
}
}
public void WaitForNonExistence(T item)
{
lock (padlock)
{
while (items.Contains(item))
{
Monitor.Wait(padlock);
}
}
}
public void WaitForAndAdd(T item)
{
lock (padlock)
{
WaitForNonExistence(item);
items.Add(item);
}
}
public void Remove(T item)
{
lock (padlock)
{
if (items.Remove(item))
{
Monitor.PulseAll(padlock);
}
}
}
}
(诚然,完全未经测试。您可能还想为等待代码指定超时...)
关于C#并发列表问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1075492/