说我有这个简单的方法:
public IEnumerable<uint> GetNumbers()
{
uint n = 0;
while(n < 100)
yield return n++;
}
你如何使这个线程安全?而且我的意思是你会得到一次枚举器,并且让多个线程处理所有数字,而不会有人重复。
我想在某个地方需要使用一个锁,但是哪个锁必须使迭代器块成为线程安全的?一般来说,如果你想要一个线程安全IEnumerable<T>
,你需要记住什么?或者更确切地说,我猜这将是一个线程安全的IEnumerator<T>
......?
答案 0 :(得分:40)
这样做存在固有问题,因为IEnumerator<T>
同时包含MoveNext()
和Current
。你真的想要一个电话,如:
bool TryMoveNext(out T value)
此时你可以原子地移动到下一个元素并获得一个值。实现它仍然能够使用yield
可能会很棘手......但我会考虑一下。我认为你需要将“非线程安全”迭代器包装在一个线程安全的迭代器中,它原子地执行MoveNext()
和Current
来实现上面显示的接口。我不知道你怎么把这个界面包回IEnumerator<T>
,以便你可以在foreach
中使用它...
如果你正在使用.NET 4.0,那么可以 能够帮助你 - 你需要解释更多关于你想要做什么的事情。
这是一个有趣的话题 - 我可能要写博客......
编辑:我现在blogged about it有两种方法。
答案 1 :(得分:2)
我刚测试了这段代码:
static IEnumerable<int> getNums()
{
Console.WriteLine("IENUM - ENTER");
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
yield return i;
}
Console.WriteLine("IENUM - EXIT");
}
static IEnumerable<int> getNums2()
{
try
{
Console.WriteLine("IENUM - ENTER");
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
yield return i;
}
}
finally
{
Console.WriteLine("IENUM - EXIT");
}
}
getNums2()始终调用代码的finally部分。如果你希望你的IEnumerable是线程安全的,那么使用ReaderWriterSlimLock,Semaphore,Monitor等添加你想要的任何线程锁而不是writelines。
答案 2 :(得分:0)
我假设你需要一个线程保存的枚举器,所以你应该实现那个。
答案 3 :(得分:0)
嗯,我不确定,但也许在调用者中有一些锁?
吃水:
Monitor.Enter(syncRoot);
foreach (var item in enumerable)
{
Monitor.Exit(syncRoot);
//Do something with item
Monitor.Enter(syncRoot);
}
Monitor.Exit(syncRoot);
答案 4 :(得分:0)
我认为你不能使yield
关键字线程安全,除非你依赖于已经线程安全的值来源:
public interface IThreadSafeEnumerator<T>
{
void Reset();
bool TryMoveNext(out T value);
}
public class ThreadSafeUIntEnumerator : IThreadSafeEnumerator<uint>, IEnumerable<uint>
{
readonly object sync = new object();
uint n;
#region IThreadSafeEnumerator<uint> Members
public void Reset()
{
lock (sync)
{
n = 0;
}
}
public bool TryMoveNext(out uint value)
{
bool success = false;
lock (sync)
{
if (n < 100)
{
value = n++;
success = true;
}
else
{
value = uint.MaxValue;
}
}
return success;
}
#endregion
#region IEnumerable<uint> Members
public IEnumerator<uint> GetEnumerator()
{
//Reset(); // depends on what behaviour you want
uint value;
while (TryMoveNext(out value))
{
yield return value;
}
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
//Reset(); // depends on what behaviour you want
uint value;
while (TryMoveNext(out value))
{
yield return value;
}
}
#endregion
}
您必须决定枚举器的每个典型启动是否应重置序列,或者客户端代码是否必须这样做。
答案 5 :(得分:-2)
每次都可以返回一个完整的序列,而不是使用yield:
return Enumerable.Range(0, 100).Cast<uint>().ToArray();