如果我在一个锁定语句中有一个收益率回报,那么锁定会在每个收益率上取消(在下面的例子中为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;
}
}
}
答案 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)
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;
}
}
}