c# - rendimientos de rendimiento dentro de la declaración de bloqueo
locking yield-return (3)
si tengo un rendimiento de rendimiento en una declaración de bloqueo, ¿el bloqueo se elimina en cada rendimiento (5 veces en el siguiente ejemplo) o solo una vez para todos los elementos de la lista?
Gracias
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;
}
}
}
@Lockszmith tiene una buena captura (+1). Solo publico esto porque encuentro su código difícil de leer. Esta es una "wiki de la comunidad" . Siéntase libre de actualizar.
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;
}
}
}
Comenzar a editar
Consulte el código en la wiki de la comunidad que proporciona @EZI, que es más fácil de leer / limpiar .
EDICIÓN FINAL
Lamento haber resucitado esto de entre los muertos, pero al leer la respuesta aceptada por Daniel y luego probarlo, pensé que al menos esas 10 personas que votan en la votación al menos deberían saber que está completamente mal.
La respuesta es: el bloqueo NUNCA se libera entre cada yeald return
.
NOTA : Sin embargo, se libera cuando finaliza el enumerador, es decir, cuando finaliza el bucle foreach.
La respuesta de Daniel es errónea al afirmar que el bloqueo se toma más de una vez. Esto se debe a que el código de Daniel no es multiproceso, siempre se computaría de la misma manera. El bloqueo en ese código se toma solo una vez, y dado que es el mismo hilo, siempre es el mismo bloqueo.
Tomé el código de @ Daniel de su respuesta y lo cambié para que funcionara con 2 subprocesos, uno para List1 y otro creado para cada iteración de List2.
Como puede ver, una vez que se inicia el subproceso t2, los subprocesos se bloquearían, ya que t2 está esperando en un bloqueo que nunca se liberaría.
El código:
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);
}
}
Edición: esta respuesta fue incorrecta, pero no puedo eliminarla porque estaba marcada como correcta. Consulte la respuesta de @Lockszmith a continuación para obtener la respuesta correcta.
Parafraseado
La cerradura NUNCA se libera entre cada retorno yeald. NOTA: Sin embargo, se libera cuando finaliza el enumerador, es decir, cuando finaliza el bucle foreach.
Edición final
Respuesta original (incorrecta):
En su escenario, el bloqueo sólo se tomará una vez. Así que en definitiva, solo una vez. Sin embargo, no estás tratando con ningún recurso compartido. Cuando empiezas a lidiar con recursos compartidos como en la aplicación de consola a continuación, suceden algunas cosas interesantes.
Verá en los resultados que el bloqueo se libera temporalmente en cada rendimiento. Además, tenga en cuenta que el bloqueo en la Lista 1 no se libera hasta que todos los elementos se hayan escrito en la consola, lo que demuestra que el método GetData () se ejecuta parcialmente con cada iteración del bucle y que el bloqueo debe liberarse temporalmente con cada declaración de rendimiento.
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);
}
}
}
Resultados:
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
Sin embargo, lo realmente genial aquí es el resultado. Tenga en cuenta que la línea "iniciar GetData" se produce después de la llamada a DataGetter (). GetData () pero antes de todo lo que ocurre dentro del método GetData (). Esto se denomina ejecución diferida y demuestra la belleza y la utilidad de la declaración de rendimiento de rendimiento: en cualquier lugar dentro de su bucle externo puede salir del bucle y no habrá más llamadas al bucle interno. Esto significa que no tiene que recorrer todo el bucle interno si no tiene que hacerlo y también significa que comenzará a obtener resultados en su bucle externo más temprano.