C#: How can I make an IEnumerable<T> thread safe?

半城伤御伤魂 提交于 2019-12-17 21:56:00

问题


Say I have this simple method:

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

How would you make this thread safe? And by that I mean that you would get that enumerator once, and have multiple threads handle all the numbers without anyone getting duplicates.

I suppose a lock needs to be used somewhere, but where must that lock be for an iterator block to be thread safe? What, in general, do you need to remember if you want a thread safe IEnumerable<T>? Or rather I guess it would be a thread safe IEnumerator<T>...?


回答1:


There's an inherent problem in doing so, because IEnumerator<T> has both MoveNext() and Current. You really want a single call such as:

bool TryMoveNext(out T value)

at that point you can atomically move to the next element and get a value. Implementing that and still being able to use yield could be tricky... I'll have a think about it though. I think you'd need to wrap the "non-threadsafe" iterator in a thread-safe one which atomically performed MoveNext() and Current to implement the interface shown above. I don't know how you'd then wrap this interface back into IEnumerator<T> so that you could use it in foreach though...

If you're using .NET 4.0, Parallel Extensions may be able to help you - you'd need to explain more about what you're trying to do though.

This is an interesting topic - I may have to blog about it...

EDIT: I've now blogged about it with two approaches.




回答2:


I just tested this bit of code:

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() always calls the finally part of the code. If you want your IEnumerable to be thread safe, add whatever thread locks you want instead of writelines, wither using ReaderWriterSlimLock, Semaphore, Monitor, etc.




回答3:


Well, i'm not sure, but maybe with some locks in the caller ?

Draft:

Monitor.Enter(syncRoot);
foreach (var item in enumerable)
{
  Monitor.Exit(syncRoot);
  //Do something with item
  Monitor.Enter(syncRoot);
}
Monitor.Exit(syncRoot);



回答4:


I was thinking that you can't make the yield keyword thread-safe, unless you make it depend on an already thread-safe source of values:

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
}

You will have to decide whether each typical initiation of an enumerator should reset the sequence, or if the client code must do that.




回答5:


You could just return a complete sequence each time rather than use yield:

return Enumerable.Range(0, 100).Cast<uint>().ToArray();



来源:https://stackoverflow.com/questions/1605745/c-how-can-i-make-an-ienumerablet-thread-safe

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!