大编辑
ConcurrentQueue 仅对 Enqueue(T) 和 T Dequeue() 操作是安全的。您正在对其进行foreach并且未在所需级别同步。在您的特定情况下,最大的问题是枚举队列(它本身就是一个集合)可能会引发众所周知的“集合已被修改”异常。为什么这是最大的问题?因为您在之后将内容添加到队列中您已将相应的对象添加到列表中(也非常需要同步列表,但是 + 最大的问题只需一个“子弹”即可解决)。在枚举集合时,很难接受另一个线程正在修改它的事实(即使在微观层面上修改是安全的 - ConcurrentQueue 就是这样做的)。
因此,您绝对需要使用另一种同步方式来同步对队列(以及中央列表)的访问(我的意思是,您也可以忘记 about ConcurrentQueue 并使用简单的队列甚至是列表,因为您永远不要让事情出队)。
所以只需执行以下操作:
public void Writer(object toWrite) {
this.rwLock.EnterWriteLock();
try {
int tailIndex = this.list.Count;
this.list.Add(toWrite);
if (..condition1..)
this.queue1.Enqueue(tailIndex);
if (..condition2..)
this.queue2.Enqueue(tailIndex);
if (..condition3..)
this.queue3.Enqueue(tailIndex);
..etc..
} finally {
this.rwLock.ExitWriteLock();
}
}
在 AccessItems 中:
public IEnumerable<object> AccessItems(int queueIndex) {
Queue<object> whichQueue = null;
switch (queueIndex) {
case 1: whichQueue = this.queue1; break;
case 2: whichQueue = this.queue2; break;
case 3: whichQueue = this.queue3; break;
..etc..
default: throw new NotSupportedException("Invalid queue disambiguating params");
}
List<object> results = new List<object>();
this.rwLock.EnterReadLock();
try {
foreach (var index in whichQueue)
results.Add(this.list[index]);
} finally {
this.rwLock.ExitReadLock();
}
return results;
}
而且,根据我对您的应用程序访问列表和各种队列的情况的全部理解,它应该是 100% 安全的。
大编辑结束
首先:你称之为 Thread-Safe 的东西是什么?埃里克·利珀特
在您的特定情况下,我想答案是否定的。
在全局上下文(实际列表)中不会出现不一致的情况。
取而代之的是,实际的读者(他们很可能与唯一的作者“冲突”)最终会在自身内部出现不一致(他们自己的堆栈含义:所有方法的局部变量、参数以及它们在堆中的逻辑隔离部分) ))。
这种“每线程”不一致的可能性(第 N 个线程想要了解 List 中元素的数量并发现该值为 39404999,尽管实际上您只添加了 3 个值)足以声明,一般来说,架构不是线程安全的(尽管您实际上并没有更改全局可访问的列表,只是通过以有缺陷的方式读取它)。
我建议您使用ReaderWriterLockSlim类。我想你会发现它适合你的需求:
private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private List<Object> items;
private ConcurrentQueue<int> queue;
private Timer timer;
private void callback(object state)
{
int index = items.Count;
this.rwLock.EnterWriteLock();
try {
// in this place, right here, there can be only ONE writer
// and while the writer is between EnterWriteLock and ExitWriteLock
// there can exist no readers in the following method (between EnterReadLock
// and ExitReadLock)
// we add the item to the List
// AND do the enqueue "atomically" (as loose a term as thread-safe)
items.Add(new object());
if (true)//some condition here
queue.Enqueue(index);
} finally {
this.rwLock.ExitWriteLock();
}
timer.Change(TimeSpan.FromMilliseconds(500), TimeSpan.FromMilliseconds(-1));
}
//This can be called from any thread
public IEnumerable<object> AccessItems()
{
List<object> results = new List<object>();
this.rwLock.EnterReadLock();
try {
// in this place there can exist a thousand readers
// (doing these actions right here, between EnterReadLock and ExitReadLock)
// all at the same time, but NO writers
foreach (var index in queue)
{
this.results.Add ( this.items[index] );
}
} finally {
this.rwLock.ExitReadLock();
}
return results; // or foreach yield return you like that more :)
}