C#中的yield return是线程安全的吗?

发布于 2024-08-03 09:55:38 字数 434 浏览 14 评论 0原文

我有以下代码:

private Dictionary<object, object> items = new Dictionary<object, object>;
public IEnumerable<object> Keys
{
    get
    {
        foreach (object key in items.Keys)
        {
            yield return key;
        }
    }
}

这是线程安全的吗?如果不是,我是否必须在循环或收益返回周围放置一个

我的意思是:

Thread1 访问 Keys 属性,而 Thread2 向底层字典添加一个项目。 Thread1 是否受到 Thread2 的添加的影响?

I have the following piece of code:

private Dictionary<object, object> items = new Dictionary<object, object>;
public IEnumerable<object> Keys
{
    get
    {
        foreach (object key in items.Keys)
        {
            yield return key;
        }
    }
}

Is this thread-safe? If not do I have to put a lock around the loop or the yield return?

Here is what I mean:

Thread1 accesses the Keys property while Thread2 adds an item to the underlying dictionary. Is Thread1 affected by the add of Thread2?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

冷心人i 2024-08-10 09:55:38

线程安全到底是什么意思?

您当然不应该在迭代字典时更改字典,无论是否在同一线程中。

如果通常在多个线程中访问字典,调用者应该取出一个锁(覆盖所有访问的同一个锁),以便他们可以在迭代结果期间锁定。

编辑:为了回应您的编辑,不,它绝不对应于锁定代码。迭代器块不会自动取出锁 - 它如何知道 syncRoot 呢?

此外,仅锁定 IEnumerable 的返回也不会使其成为线程安全的 - 因为锁定仅影响它返回序列的时间段,而不是迭代的时间段。

What exactly do you mean by thread-safe?

You certainly shouldn't change the dictionary while you're iterating over it, whether in the same thread or not.

If the dictionary is being accessed in multiple threads in general, the caller should take out a lock (the same one covering all accesses) so that they can lock for the duration of iterating over the result.

EDIT: To respond to your edit, no it in no way corresponds to the lock code. There is no lock automatically taken out by an iterator block - and how would it know about syncRoot anyway?

Moreover, just locking the return of the IEnumerable<TKey> doesn't make it thread-safe either - because the lock only affects the period of time when it's returning the sequence, not the period during which it's being iterated over.

星星的軌跡 2024-08-10 09:55:38

查看这篇文章,了解 yield 关键字在幕后发生的情况:

C# Yield 关键字的幕后

简而言之 - 编译器采用您的 Yield 关键字并在 IL 中生成整个类来支持该功能。您可以查看跳转后的页面并查看生成的代码...该代码看起来像是跟踪线程 ID 以确保安全。

Check out this post on what happens behind the scenes with the yield keyword:

Behind the scenes of the C# yield keyword

In short - the compiler takes your yield keyword and generates an entire class in the IL to support the functionality. You can check out the page after the jump and check out the code that gets generated...and that code looks like it tracks thread id to keep things safe.

£冰雨忧蓝° 2024-08-10 09:55:38

好的,我做了一些测试并得到了一个有趣的结果。

看起来这更多是底层集合的枚举器的问题,而不是 yield 关键字的问题。枚举器(实际上是其 MoveNext 方法)会抛出(如果正确实现)InvalidOperationException,因为枚举已更改。根据 MoveNext 方法的 MSDN 文档这是预期的行为。

因为通过集合进行枚举通常不是线程安全的,所以 yield return 也不是。

OK, I did some testing and got an interesting result.

It seems that it is more an issue of the enumerator of the underlying collection than the yield keyword. The enumerator (actually its MoveNext method) throws (if implemented correctly) an InvalidOperationException because the enumeration has changed. According to the MSDN documentation of the MoveNext method this is the expected behavior.

Because enumerating through a collection is usually not thread-safe a yield return is not either.

望她远 2024-08-10 09:55:38

我相信是这样,但我找不到证实这一点的参考文献。每次任何线程在迭代器上调用 foreach 时,都应创建底层 IEnumerator 的新线程 local* 实例,因此不应存在两个线程可能发生冲突的任何“共享”内存状态...

  • Thread Local - 在某种意义上它的引用变量的作用域是该线程上的方法堆栈帧

I believe it is, but I cannot find a reference that confirms it. Each time any thread calls foreach on an iterator, a new thread local* instance of the underlying IEnumerator should get created, so there should not be any "shared" memory state that two threads can conflict over...

  • Thread Local - In the sense that it's reference variable is scoped to a method stack frame on that thread
一向肩并 2024-08-10 09:55:38

我相信yield实现是线程安全的。事实上,您可以在家运行这个简单的程序,您会注意到 listInt() 方法的状态已为每个线程正确保存和恢复,而没有其他线程的边缘效应。

public class Test
{
    public void Display(int index)
    {
        foreach (int i in listInt())
        {
            Console.WriteLine("Thread {0} says: {1}", index, i);
            Thread.Sleep(1);
        }

    }

    public IEnumerable<int> listInt()
    {
        for (int i = 0; i < 5; i++)
        {
            yield return i;
        }
    }
}

class MainApp
{
    static void Main()
    {
        Test test = new Test();
        for (int i = 0; i < 4; i++)
        {
            int x = i;
            Thread t = new Thread(p => { test.Display(x); });
            t.Start();
        }

        // Wait for user
        Console.ReadKey();
    }
}

I believe yield implementation is thread-safe. Indeed, you can run that simple program at home and you will notice that the state of the listInt() method is correctly saved and restored for each thread without edge effect from other threads.

public class Test
{
    public void Display(int index)
    {
        foreach (int i in listInt())
        {
            Console.WriteLine("Thread {0} says: {1}", index, i);
            Thread.Sleep(1);
        }

    }

    public IEnumerable<int> listInt()
    {
        for (int i = 0; i < 5; i++)
        {
            yield return i;
        }
    }
}

class MainApp
{
    static void Main()
    {
        Test test = new Test();
        for (int i = 0; i < 4; i++)
        {
            int x = i;
            Thread t = new Thread(p => { test.Display(x); });
            t.Start();
        }

        // Wait for user
        Console.ReadKey();
    }
}
离笑几人歌 2024-08-10 09:55:38
class Program
{
    static SomeCollection _sc = new SomeCollection();

    static void Main(string[] args)
    {
        // Create one thread that adds entries and
        // one thread that reads them
        Thread t1 = new Thread(AddEntries);
        Thread t2 = new Thread(EnumEntries);

        t2.Start(_sc);
        t1.Start(_sc);
    }

    static void AddEntries(object state)
    {
        SomeCollection sc = (SomeCollection)state;

        for (int x = 0; x < 20; x++)
        {
            Trace.WriteLine("adding");
            sc.Add(x);
            Trace.WriteLine("added");
            Thread.Sleep(x * 3);
        }
    }

    static void EnumEntries(object state)
    {
        SomeCollection sc = (SomeCollection)state;
        for (int x = 0; x < 10; x++)
        {
            Trace.WriteLine("Loop" + x);
            foreach (int item in sc.AllValues)
            {
                Trace.Write(item + " ");
            }
            Thread.Sleep(30);
            Trace.WriteLine("");
        }
    }
}

class SomeCollection
{
    private List<int> _collection = new List<int>();
    private object _sync = new object();

    public void Add(int i)
    {
        lock(_sync)
        {
            _collection.Add(i);
        }
    }


    public IEnumerable<int> AllValues
    {
        get
        {
            lock (_sync)
            {
                foreach (int i in _collection)
                {
                    yield return i;
                }
            }
        }
    }
}
class Program
{
    static SomeCollection _sc = new SomeCollection();

    static void Main(string[] args)
    {
        // Create one thread that adds entries and
        // one thread that reads them
        Thread t1 = new Thread(AddEntries);
        Thread t2 = new Thread(EnumEntries);

        t2.Start(_sc);
        t1.Start(_sc);
    }

    static void AddEntries(object state)
    {
        SomeCollection sc = (SomeCollection)state;

        for (int x = 0; x < 20; x++)
        {
            Trace.WriteLine("adding");
            sc.Add(x);
            Trace.WriteLine("added");
            Thread.Sleep(x * 3);
        }
    }

    static void EnumEntries(object state)
    {
        SomeCollection sc = (SomeCollection)state;
        for (int x = 0; x < 10; x++)
        {
            Trace.WriteLine("Loop" + x);
            foreach (int item in sc.AllValues)
            {
                Trace.Write(item + " ");
            }
            Thread.Sleep(30);
            Trace.WriteLine("");
        }
    }
}

class SomeCollection
{
    private List<int> _collection = new List<int>();
    private object _sync = new object();

    public void Add(int i)
    {
        lock(_sync)
        {
            _collection.Add(i);
        }
    }


    public IEnumerable<int> AllValues
    {
        get
        {
            lock (_sync)
            {
                foreach (int i in _collection)
                {
                    yield return i;
                }
            }
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文