2009-03-24 8 views
7

Editar para intro:
Sabemos que un parámetro de referencia en C# pasa una referencia a una variable, lo que permite la propia variable externa a ser cambiado dentro de un método llamado. Pero, ¿se maneja la referencia como un puntero C (leyendo los contenidos actuales de la variable original con cada acceso a ese parámetro y cambiando la variable original con cada modificación del parámetro), o puede el método invocado basarse en una referencia consistente para el duración de la llamada? El primero saca a relucir algunos problemas de seguridad de hilo. En particular:¿Los parámetros de las referencias .NET son seguros para la ejecución de subprocesos o son vulnerables al acceso multiproceso no seguro?

He escrito un método estático en C# que pasa un objeto por referencia:

public static void Register(ref Definition newDefinition) { ... } 

La persona que llama proporciona una completado pero que aún no es registrado Definition objeto, y después de cierta consistencia que la comprobación "registrar" la definición que proporcionaron. Sin embargo, si ya hay una definición con la misma clave, no puede registrar la nueva y en su lugar su referencia se actualiza al Definition "oficial" para esa clave.

Queremos que sea rigurosamente seguro para subprocesos, pero nos viene a la mente un escenario patológico. Supongamos que el cliente (usando nuestra biblioteca) comparte la referencia de un modo no seguro para subprocesos, tales como el uso de un miembro estático en lugar de una variable local:

private static Definition riskyReference = null; 

Si un hilo establece riskyReference = new Definition("key 1");, llena la definición, y llama a nuestro Definition.Register(ref riskyReference);, mientras que otro subproceso también decide establecer riskyReference = new Definition("key 2");, ¿estamos seguros de que en nuestro método de Registro la referencia newDefinition que estamos manejando no será modificada por otros hilos (porque la referencia al objeto se copió en y será ¿se copian cuando volvemos?), o puede ese otro hilo reemplazar el objeto sobre nosotros en el medio de nuestra ejecución (si estamos haciendo referencia a un puntero a la ubicación de almacenamiento original ???) y así romper nuestra comprobación de la cordura?

Tenga en cuenta que esto es diferente de los cambios en el objeto subyacente en sí, que por supuesto son posibles para un tipo de referencia (clase), pero pueden protegerse fácilmente mediante el bloqueo apropiado dentro de esa clase. Sin embargo, no podemos guardar cambios en el espacio variable del cliente externo. Tendríamos que hacer nuestra propia copia del parámetro en la parte superior del método y sobrescribir el parámetro en la parte inferior (por ejemplo), pero parece que tiene más sentido para el compilador hacer por nosotros dada la locura de manejar una referencia insegura.

Por lo tanto, tendería a pensar que el compilador puede copiar y copiar la referencia para que el método maneje una referencia consistente al objeto original (hasta que cambie su propia referencia cuando lo desee) independientemente de lo que pueda estar pasando con la ubicación original en otros hilos. Pero estamos teniendo problemas para encontrar una respuesta definitiva sobre ese punto en la documentación y discusión de los parámetros de referencia.

¿Alguien puede calmar mi preocupación con una cita definitiva?

Editar para la conclusión: (! Gracias Marc)
Después de haber confirmado con un ejemplo de código multi-hilo y pensar en ello aún más, tiene sentido que de hecho es la no-automática-multi-hilo comportamiento cuales Me preocuparon. Un punto de "ref" es pasar estructuras grandes por referencia en lugar de copiarlas.Otra razón es que puede querer para configurar una monitorización a largo plazo de una variable y debe pasarle una referencia que verá los cambios a la variable (por ejemplo, cambiar entre nulo y un objeto activo), que es automático copiar/enviar/copiar no permitiría.

Por lo tanto, para que nuestra Register método robusto frente a la locura cliente, podríamos implementarlo como:

public static void Register(ref Definition newDefinition) { 
    Definition theDefinition = newDefinition; // Copy in. 
    //... Sanity checks, actual work... 
    //...possibly changing theDefinition to a new Definition instance... 
    newDefinition = theDefinition; // Copy out. 
} 

Habían todavía tienen sus propios problemas de threads en cuanto a lo que terminan siendo, pero al menos su locura no rompería nuestro propio proceso de control de la cordura y posiblemente pasaría un mal estado más allá de nuestros controles.

Respuesta

7

Cuando usa ref, está pasando la dirección del campo/variable de la persona que llama. Por lo tanto, sí: dos subprocesos pueden competir en el campo/variable, pero solo si ambos hablan con ese campo/variable. Si tienen un campo/variable diferente para la misma instancia, entonces las cosas son sensatas (suponiendo que sea inmutable).

Por ejemplo; en el siguiente código, Registerver ver los cambios que Mutate hace a la variable (cada instancia de objeto es efectivamente inmutable).

using System; 
using System.Threading; 
class Foo { 
    public string Bar { get; private set; } 
    public Foo(string bar) { Bar = bar; } 
} 
static class Program { 
    static Foo foo = new Foo("abc"); 
    static void Main() { 
     new Thread(() => { 
      Register(ref foo); 
     }).Start(); 
     for (int i = 0; i < 20; i++) { 
      Mutate(ref foo); 
      Thread.Sleep(100); 
     } 
     Console.ReadLine(); 
    } 
    static void Mutate(ref Foo obj) { 
     obj = new Foo(obj.Bar + "."); 
    } 
    static void Register(ref Foo obj) { 
     while (obj.Bar.Length < 10) { 
      Console.WriteLine(obj.Bar); 
      Thread.Sleep(100); 
     } 
    } 
} 
+1

bien, ese ejemplo * no * mostrar que los parámetros de árbitro en C# * no * copias compatibles con el proceso, por lo que el cliente haciendo algo estúpido y patológica podría cambiar qué objeto estamos manejo en el medio de nuestro método. Tendríamos que hacer nuestra propia copia de entrada y salida si queremos protegernos de ello. ¡Gracias! –

6

No, no es "copiar, copiar". En cambio, la variable en sí misma se transfiere efectivamente. No el valor, sino la variable misma. Los cambios realizados durante el método son visibles para cualquier otra persona que observe la misma variable.

Esto se puede ver sin ningún tipo de roscado que se trate:

using System; 

public class Test 
{ 
    static string foo; 

    static void Main(string[] args) 
    { 
     foo = "First"; 
     ShowFoo(); 
     ChangeValue(ref foo); 
     ShowFoo(); 
    } 

    static void ShowFoo() 
    { 
     Console.WriteLine(foo); 
    } 

    static void ChangeValue(ref string x) 
    { 
     x = "Second"; 
     ShowFoo(); 
    } 
} 

La salida de este es Primera, Segunda, Segunda - la llamada a ShowFoo()dentro ChangeValue muestra que el valor de foo ya ha cambiado, que es exactamente la situación que le preocupa.

La solución

Hacer Definition inmutable si no era antes, y cambiar su método de firma a:

public static Definition Register(Definition newDefinition) 

Entonces la persona que llama puede sustituir su variable si quieren, pero su la memoria caché no puede contaminarse con un hilo astuto. La persona que llama haría algo como:

myDefinition = Register(myDefinition); 
+0

Fuimos específicamente al parámetro ref para evitar el requisito de que la persona que llama reemplace la variable con los resultados de la llamada, ya que podrían no hacerlo y normalmente funcionaría bien (usa el mismo objeto), pero a veces podría morderlos. Pero tienes razón de que evitaría este problema. –

+0

Podrían usar una variable local en lugar de la variable compartida correcta, lo que también los afectaría. No puede garantizar que la persona que llama sea sensata, pero * puede * garantizar que su propio código se comporte con sensatez. –

+0

Sí, pero queremos que nuestra API sea tan infalible como podamos hacerlo, no tendemos a hacer que la arruinen.;-) Mientras usen una variable local para la nueva definición (como deberían), el uso de ref les facilita el proceso. Mi preocupación es la paranoia sobre el uso patológico que nos rompe. –

Cuestiones relacionadas