2010-10-10 11 views
7

Sé que las expresiones .NET lambda pueden capturar variables externas. Sin embargo, he visto muchas veces que las variables se pasan explícitamente a la expresión lambda como parámetro, y la biblioteca .NET también parece soportar eso (por ejemplo, ThreadPool.QueueUserWorkItem).Expresiones lambda, variables capturadas y subprocesamiento

Mi pregunta es, ¿cuáles son las limitaciones de estas capturas? ¿Qué hay de las lambdas que se ejecutan realmente en un subproceso diferente del que se crearon (por ejemplo, ThreadPool.QueueUserWorkItem o Thread) o lambas que actúan como devoluciones de llamada (es decir, se invocan más adelante)?

En general, ¿cuándo debo confiar en las variables capturadas y cuándo usar los parámetros explícitos? Por ejemplo:

public void DoStuff() 
{ 
    string message = GetMessage(); 

    ThreadPool.QueueUserWorkItem(s => SendMessage(message)); // use captured variable 
    // -- OR -- 
    ThreadPool.QueueUserWorkItem(s => 
      { 
       string msg = (string)s; 
       SendMessage(msg); 
      }, message); // use explicit parameter 
} 

¡Gracias!

Actualización: se corrigió el segundo ejemplo de ThreadPool.QueueUserWorkItem.

Respuesta

9

creo que en su primer ejemplo., Quiere decir

QueueUserWorkItem(() => SendMessage(message)); 

En su segundo artículo, ¿de dónde viene el parámetro s viene? Creo que te refieres

QueueUserWorkItem(s => {SendMessage((string)s);} , message); 

Ahora, estos dos ambos funcionan de manera equivalente, pero

  • En el primer caso: el parámetro message se copia del alcance de este DoStuff método y almacena directamente en su expresión lambda sí mismo, como un cierre. La lambda tiene guarda una copia de message.

  • En el segundo caso: message se envía a la Queue, y la cola mantiene asimiento de él (junto con el lambda), hasta que el lambda se llama. Es pasado en el momento de ejecutar la lambda , a la lambda.

Yo diría que el segundo caso es más flexible de programación, ya que en teoría podría permitirle cambiar más tarde el valor del parámetro message antes de la lambda se llama. Sin embargo, el primer método es más fácil de leer y es más inmune a los efectos secundarios. Pero en la práctica, ambos funcionan.

+0

Tienes razón, me perdí el parámetro en la segunda parte del ejemplo. Entonces, según tengo entendido, en la práctica no importa cuál use, pero me recomendaría ir con las variables capturadas, debido a su simplicidad (y legibilidad). ¡Muchas gracias por su respuesta detallada! – ShdNx

5

Para su ejemplo (una referencia a un objeto de cadena inmutable) no hace absolutamente ninguna diferencia.

Y en general, hacer una copia de una referencia no va a hacer mucha diferencia. Sí importa cuando se trabaja con tipos de valores:

double value = 0.1; 

ThreadPool.QueueUserWorkItem(() => value = Process(value)); // use captured variable 

// -- OR -- 

ThreadPool.QueueUserWorkItem(() => 
     { 
      double val = value;  // use explicit parameter 
      val = Process(val);  // value is not changed 
     }); 

// -- OR -- 

ThreadPool.QueueUserWorkItem((v) => 
     { 
      double val = (double)v; // explicit var for casting 
      val = Process(val);  // value is not changed 
     }, value); 

La primera versión no es seguro para subprocesos, la segunda y la tercera podría ser.

La última versión usa el parámetro object state que es una característica Fx2 (pre-LINQ).

+0

Debo admitir que encuentro su código de ejemplo confuso. En la primera parte, estás usando el parámetro, pero no lo pasas para hacer el lambda. Pero eso no está usando una variable capturada (según mi entendimiento), porque eso significaría usar directamente la variable de valor. Su segundo ejemplo señala cómo se comportan los tipos inmutables, lo cual está bien, pero no me ayuda a entender la diferencia. ¿Podría actualizar su respuesta para aclarar esto? – ShdNx

+0

@Shnx, tienes razón, escribir en mal estado. Lo arreglaré. –

+0

Gracias, está claro ahora. Lo único es que también debe actualizar los comentarios en el código. +1 por mencionar threadsafety (aunque solo funciona con tipos inmutables), pero acepté la respuesta de Sanjay Manohar porque era más rápido, claro y detallado. ¡Gracias también por tu ayuda! – ShdNx

1
  1. Si desea que los funtores anónimos hagan referencia al mismo estado, capture un identificador del contexto común.
  2. Si desea lambdas sin estado (como recomendaría en una aplicación concurrente) use copias locales.