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)
编辑: 这个答案是错误的,但我不能删除它,因为它被标记为正确.请参阅下面的@ 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语句的美妙和实用性:在外部循环中的任何地方都可以突破循环,并且不再需要调用内部循环.这意味着如果不需要,则不必迭代整个内循环,这也意味着您将开始先将结果传递到外循环.
@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)