C#:如何使IEnumerable <t>线程安全?</t>

时间:2009-10-22 08:25:23

标签: c# thread-safety ienumerable

说我有这个简单的方法:

public IEnumerable<uint> GetNumbers()
{
    uint n = 0;
    while(n < 100)
        yield return n++;
}

你如何使这个线程安全?而且我的意思是你会得到一次枚举器,并且让多个线程处理所有数字,而不会有人重复。

我想在某个地方需要使用一个锁,但是哪个锁必须使迭代器块成为线程安全的?一般来说,如果你想要一个线程安全IEnumerable<T>,你需要记住什么?或者更确切地说,我猜这将是一个线程安全的IEnumerator<T> ......?

6 个答案:

答案 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();