2009-08-25 11 views
6

Para el siguiente código me sale un error de tiempo de compilación, *¿Por qué no se permite el bloqueo (<integer var>), pero se permite Monitor.Enter (<integer var>)?

'int' no es un tipo de referencia como requerido por la instrucción lock

int i = 0; 
lock(i); 

Pero no hay errores para esto:

int i = 0; 
Monitor.Enter(i); 

Entiendo que un tipo de valor no se debe utilizar para bloquear debido a las complicaciones que surgen du e al boxeo. Pero, entonces, ¿por qué funciona con Monitor?

Respuesta

15

La razón de por qué es que el bloqueo es una construcción de lenguaje y el compilador elige imponer semántica adicional en la expresión. Monitor.Enter es simplemente una llamada a un método y el compilador C# no hace un caso especial de la llamada de ninguna manera y, por lo tanto, pasa por una resolución de sobrecarga normal y un boxeo.

0

yo diría que es porque Monitor.Enter() es una llamada a un método, por lo que el compilador realiza el boxeo de forma automática, mientras que lock() es un elemento sintáctico, por lo que el compilador puede verificar y generar un error en los tipos de valor.

+0

No creo que haya ninguna comprobación. No se ha expandido el bloqueo con un Monito.Ingrese en un bloque try y Monitor.Exit en el bloque finally. – Sandbox

+1

no, se amplía a eso, pero primero el compilador detecta si estás siendo tonto en el camino ... – ShuggyCoUk

11

Definitivamente no debe usar Monitor.Enter en un int. La razón por la que funciona es porque el int está encuadrado, de modo que a menos que almacene una referencia al valor encuadrado, estará bloqueando un objeto temporal, lo que significa que no puede llamar al Monitor.Exit sin obtener una excepción.

La forma recomendada de hacer el bloqueo es crear un private readonly object y bloquearlo. Para un método estático, puede usar un private static object.

3

Solo por curiosidad, ¿qué haces con la variable 'i' que requiere que esté bloqueada? Puede ser más eficaz utilizar la clase Interlocked si todo su hacer es un incremento o algo:

Interlocked.Increment(i); // i++ in a thread safe manner 

La clase Interlocked es la herramienta de sincronización de hilo de peso ligero que proporciona .NET, y por incrementos simples, decrementos, lee , o intercambios, es la mejor opción.

Si está intentando sincronizar un bloque de comportamiento, entonces simplemente me gustaría crear un objeto que se puede utilizar como una raíz de sincronización:

object syncRoot = new object(); 

// ... 

lock(syncRoot) 
{ 
    // put synced behavior here 
} 
+0

puramente por interés ... conozco la clase Interbloqueada. Gracias. – Sandbox

5

La especificación para el compilador define the behaviour of lock like so:

El tipo de tiempo de compilación de la expresión de una sentencia de bloqueo será un tipo de referencia o un parámetro> (§25.1.1) que se sabe que es un tipo de referencia. Es un error en tiempo de compilación para el tipo de tiempo de compilación de la expresión para denotar un tipo de valor.

Luego define lo que es equivalente a siempre que compila

Desde Monitor.Salir es solo una llamada a un método sin ninguna restricción, no impedirá que el compilador pegue automáticamente el int y siga su camino feliz (y muy) equivocado.

lock no es simplemente azúcar sintáctica de la misma manera foreach no es simplemente azúcar sintáctica. La transformación IL resultante es no presentada al resto del código como si fuera lo que se había escrito.

En foreach es ilegal modificar la variable de iteración (a pesar de que no haya nada en el nivel de IL en el código de salida resultante que podría evitar esto). En el bloqueo, el compilador evita los tipos de valores conocidos de tiempo de compilación, nuevamente a pesar de que IL no se preocupa por esto.

Como acotación al margen:
En teoría, el compilador podría 'bendecido' con el conocimiento íntimo de este (y otros) los métodos para que se descubrió casos obvios de que esto ocurra, pero fundamentalmente es imposible al punto de siempre esto en tiempo de compilación (considere un objeto pasado desde otro método, ensamblado o mediante reflexión) por lo que molestarse en detectar cualquiera de estos casos probablemente sea contraproducente.

Cuanto más sepa el compilador sobre las funciones internas de la API, más problemas tendrá si desea modificar la API en el futuro.

Es posible, por ejemplo, que se pueda agregar una sobrecarga de Monitor.Enter() que tomó un int y bloqueado en un mutex de ancho de proceso asociado con el valor int.
Esto se ajustaría a las especificaciones del monitor (aunque probablemente sería espantoso) pero causaría problemas masivos para el compilador anterior que todavía evitaba alegremente una operación que se había vuelto legal.

+0

Buena respuesta. Gracias. – Sandbox

Cuestiones relacionadas