yield语句中的yield返回

Day*_*One 21 c# locking yield-return deferred-execution

如果我在一个锁定语句中有一个收益率回报,锁定会在每个收益率上取消(在下面的例子中为5次),或者只对列表中的所有项目取消一次?

谢谢

    private List<string> _data = new List<string>(){"1","2","3","4","5"};
    private object _locker =new object();
    public IEnumerable<string> GetData()
    {
        lock (_locker)
        {
            foreach (string s in _data)
            {
                yield return s;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

Loc*_*ith 28

BEGIN EDIT
请参阅@EZI提供社区维基中的代码,该代码更易于阅读/更清晰.
结束编辑

很抱歉从死里复活这个,但是阅读丹尼尔接受的答案,然后自己测试我虽然至少那10个投票的人至少应该知道这是完全错误的.

答案是:锁之间永​​远不会释放yeald return.
注意:然而,当枚举器完成时,即foreach循环结束时,它会被释放.

丹尼尔的回答是错误的,声称锁定不止一次.那是因为Daniel的代码不是多线程的,它总是以相同的方式计算.该代码中的锁定只采用一次,因为它是相同的线程,所以它始终是相同的锁.

我从他的答案中获取了@Daniel的代码,并将其更改为使用2个线程,一个用于List1,另一个线程为List2的每次迭代创建.

正如您所看到的,一旦t2线程启动,线程将死锁,因为t2正在等待永远不会被释放的锁.

代码:

void Main()
{
    object locker = new object();
    IEnumerable<string> myList0 = new DataGetter().GetData(locker, "List 0");
    IEnumerable<string> myList1 = new DataGetter().GetData(locker, "List 1");
    IEnumerable<string> myList2 = new DataGetter().GetData(locker, "List 2");

    Console.WriteLine("start Getdata");
    // Demonstrate that breaking out of a foreach loop releasees the lock
    var t0 = new Thread(() => {
        foreach( var s0 in myList0 )
        {
            Console.WriteLine("List 0 {0}", s0);
            if( s0 == "2" ) break;
        }
    });
    Console.WriteLine("start t0");
    t0.Start();
    t0.Join(); // Acts as 'wait for the thread to complete'
    Console.WriteLine("end t0");

    // t1's foreach loop will start (meaning previous t0's lock was cleared
    var t1 = new Thread(() => {
        foreach( var s1 in myList1)
        {
            Console.WriteLine("List 1 {0}", s1);
            // Once another thread will wait on the lock while t1's foreach
            // loop is still active a dead-lock will occure.
            var t2 = new Thread(() => {
                foreach( var s2 in myList2 )
                {
                    Console.WriteLine("List 2 {0}", s2);
                }
            } );
            Console.WriteLine("start t2");          
            t2.Start();
            t2.Join();
            Console.WriteLine("end t2");            
        }
    });
    Console.WriteLine("start t1");
    t1.Start();
    t1.Join();
    Console.WriteLine("end t1");
    Console.WriteLine("end GetData");
}

void foreachAction<T>( IEnumerable<T> target, Action<T> action )
{
    foreach( var t in target )
    {
        action(t);
    }
}

public class DataGetter
{
    private List<string> _data = new List<string>() { "1", "2", "3", "4", "5" };

    public IEnumerable<string> GetData(object lockObj, string listName)
    {
        Console.WriteLine("{0} Starts", listName);
        lock (lockObj)
        {
            Console.WriteLine("{0} Lock Taken", listName);
            foreach (string s in _data)
            {
                yield return s;
            }
        }
        Console.WriteLine("{0} Lock Released", listName);
    }
}
Run Code Online (Sandbox Code Playgroud)


Dan*_*son 8

编辑: 这个答案是错误的,但我不能删除它,因为它被标记为正确.请参阅下面的@ Lockszmith的答案以获得正确的答案.

转述:

锁定永远不会在每次回归之间释放.注意:然而,当枚举器完成时,即foreach循环结束时,它会被释放.

结束编辑

原答案(错误):

在您的方案中,锁定只会被执行一次.所以简而言之,只有一次.但是,您不处理任何共享资源.当您开始处理下面的控制台应用程序中的共享资源时,会发生一些有趣的事情.

您将从结果中看到锁定在每个产量上暂时释放.另请注意,在将所有项目都写入控制台之前,列表1上的锁定不会被释放,这表明GetData()方法是在循环的每次迭代中部分执行的,并且必须临时释放锁定屈服声明.

    static void Main(string[] args)
    {
        object locker = new object();
        IEnumerable<string> myList1 = new DataGetter().GetData(locker, "List 1");
        IEnumerable<string> myList2 = new DataGetter().GetData(locker, "List 2");
        Console.WriteLine("start Getdata");
        foreach (var x in myList1)
        {
            Console.WriteLine("List 1 {0}", x);
            foreach(var y in myList2)
            {
                Console.WriteLine("List 2 {0}", y);
            }
        }
        Console.WriteLine("end GetData");
        Console.ReadLine();
    }

    public class DataGetter
    {
        private List<string> _data = new List<string>() { "1", "2", "3", "4", "5" };

        public IEnumerable<string> GetData(object lockObj, string listName)
        {
            Console.WriteLine("{0} Starts", listName);
            lock (lockObj)
            {
                Console.WriteLine("{0} Lock Taken", listName);
                foreach (string s in _data)
                {
                    yield return s;
                }
            }
            Console.WriteLine("{0} Lock Released", listName);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

            start Getdata
            List 1 Starts
            List 1 Lock Taken
            List 1 1
            List 2 Starts
            List 2 Lock Taken
            List 2 1
            List 2 2
            List 2 3
            List 2 4
            List 2 5
            List 2 Lock Released
            List 1 2
            List 2 Starts
            List 2 Lock Taken
            List 2 1
            List 2 2
            List 2 3
            List 2 4
            List 2 5
            List 2 Lock Released
            List 1 3
            List 2 Starts
            List 2 Lock Taken
            List 2 1
            List 2 2
            List 2 3
            List 2 4
            List 2 5
            List 2 Lock Released
            List 1 4
            List 2 Starts
            List 2 Lock Taken
            List 2 1
            List 2 2
            List 2 3
            List 2 4
            List 2 5
            List 2 Lock Released
            List 1 5
            List 2 Starts
            List 2 Lock Taken
            List 2 1
            List 2 2
            List 2 3
            List 2 4
            List 2 5
            List 2 Lock Released
            List 1 Lock Released
            end GetData
Run Code Online (Sandbox Code Playgroud)

但是,他真的很酷的事情就是结果.请注意,在调用DataGetter().GetData()之后,但在GetData()方法中出现的所有内容之前,都会出现"start GetData"行.这称为延迟执行,它演示了yield return语句的美妙和实用性:在外部循环中的任何地方都可以突破循环,并且不再需要调用内部循环.这意味着如果不需要,则不必迭代整个内循环,这也意味着您将开始先将结果传递到外循环.


EZI*_*EZI 5

@Lockszmith 有一个很好的收获(+1)。我只发布这个,因为我发现他的代码很难阅读。这是一个“社区维基”。随意更新。

object lockObj = new object();

Task.Factory.StartNew((_) =>
{
    System.Diagnostics.Debug.WriteLine("Task1 started");
    var l1 = GetData(lockObj, new[] { 1, 2, 3, 4, 5, 6, 7, 8 }).ToList();
}, TaskContinuationOptions.LongRunning);

Task.Factory.StartNew((_) =>
{
    System.Diagnostics.Debug.WriteLine("Task2 started");
    var l2 = GetData(lockObj, new[] { 10, 20, 30, 40, 50, 60, 70, 80 }).ToList();
}, TaskContinuationOptions.LongRunning);
Run Code Online (Sandbox Code Playgroud)
public IEnumerable<T> GetData<T>(object lockObj, IEnumerable<T> list)
{
    lock (lockObj)
    {
        foreach (T x in list)
        {
            System.Diagnostics.Debug.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId + " returned "  + x );
            Thread.Sleep(1000);
            yield return x;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)