yield语句中的yield返回

时间:2010-05-17 08:30:25

标签: 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;
            }
        }
    }

3 个答案:

答案 0 :(得分:28)

开始编辑
请参阅community wiki provided by @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);
    }
}

答案 1 :(得分: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);
        }
    }
}

结果:

            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

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

答案 2 :(得分:4)

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

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);

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;
        }
    }
}