qué que objetos método mutables inmutables inmutabilidad evento clases c# .net multithreading

c# - que - objetos mutables e inmutables java



Cómo congelar una paleta en.NET(hacer una clase inmutable) (12)

Tal vez mi pregunta no era clara, pero estoy buscando en particular por qué los intentos anteriores son buenos o malos. Tenga en cuenta que estoy hablando aquí sobre un escenario de un solo escritor que escribe y luego se congela antes de cualquier lectura simultánea. Creo que el intento 1 está bien, pero me gustaría saber exactamente por qué (ya que me pregunto si las lecturas podrían optimizarse de alguna manera, por ejemplo). Me importa menos si esta es una buena práctica de diseño o no, pero más sobre el aspecto real de la misma.

Ok, ahora entiendo mejor lo que estás haciendo y buscando en una respuesta. Permítanme explicar mi respuesta anterior para promover el uso de bloqueos abordando primero cada uno de sus intentos.

Intento 1:

El enfoque de usar una clase simple que no tiene primitivas de sincronización de ninguna forma es completamente viable en su ejemplo. Dado que el hilo ''autor'' es el único hilo que tiene acceso a esta clase durante su estado de mutación, esto debería ser seguro. Si solo se puede ''acceder'' a otro subproceso antes de que la clase se ''congele'', necesitaría proporcionar sincronización. Esencialmente, no es posible que un hilo tenga un caché de algo que nunca ha visto.

Aparte de un hilo que tiene una copia en caché del estado interno de esta lista, hay otro problema de concurrencia con el que debería preocuparse. Debería considerar escribir el reordenamiento por el hilo de autoría. Tu solución de ejemplo no tiene suficiente código para abordar esto, pero el proceso de entregar esta lista "congelada" a otro hilo es el corazón del problema. ¿Estás usando Interlocked.Exchange o escribiendo en un estado volátil?

Todavía defiendo que ese no es el mejor enfoque simplemente porque no hay garantía de que otro hilo no haya visto la instancia mientras está mutando.

Intento 2:

Mientras que el intento 2 no debe ser usado. Si está utilizando escrituras atómicas a un miembro, también debería usar lecturas atómicas. Nunca recomendaría uno sin el otro ya que sin ambos lee y escribe siendo atómico, no ha ganado nada. La aplicación correcta de lecturas atómicas y escrituras es su "Intento 3".

Intento 3:

Esto garantizará que se lanza una excepción si un hilo ha intentado mutar una lista congelada. Sin embargo, no hace ninguna afirmación de que una lectura solo sea aceptable en una instancia congelada. Esto, en mi humilde opinión, es tan malo como acceder a nuestra variable _isFrozen con _isFrozen atómicos y no atómicos. Si va a decir que es importante proteger las escrituras, siempre debe salvaguardar las lecturas. Uno sin el otro es simplemente ''extraño''.

Pasar por alto mi propio sentimiento hacia escribir código que gaurds escribe pero no lee, este es un enfoque aceptable dados sus usos específicos. Tengo un escritor, escribo, me congelo, luego lo pongo a disposición de los lectores. En este escenario, el código funciona correctamente. _isFrozen en la operación atómica en el conjunto de _isFrozen para proporcionar la barrera de memoria requerida antes de _isFrozen la clase a otro hilo.

En pocas palabras, este enfoque funciona, pero de nuevo si un hilo tiene una instancia que no está congelada, se va a romper.

Intento 4:

Mientras que en el fondo esto es casi lo mismo que el intento 3 (dado un escritor), hay una gran diferencia. En este ejemplo, si _isFrozen en el lector, cada acceso requerirá una barrera de memoria. Esto es una sobrecarga innecesaria una vez que la lista está congelada.

Aún así, tiene el mismo problema que el intento 3 en el sentido de que no se realizan afirmaciones sobre el estado de _isFrozen durante la lectura, por lo que el rendimiento debe ser idéntico en el uso de ejemplo.

Intento 5:

Como dije, esta es mi preferencia dada la modificación para que aparezca como aparece en mi otra respuesta.

Intento 6:

Es esencialmente lo mismo que # 4.

Intento 7:

Puede resolver sus necesidades específicas con Thread.MemoryBarrier . Esencialmente, utiliza el código de Attempt 1, crea la instancia, llama a Freeze() , agrega su Thread.MemoryBarrier y luego comparte la instancia (o la comparte dentro de un bloqueo). Esto debería funcionar muy bien, nuevamente solo bajo su caso de uso limitado.

Intento 8:

Sin saber más sobre esto, no puedo aconsejar sobre el costo de la copia.

Resumen

De nuevo, prefiero usar una clase que tenga alguna garantía de enhebrado o ninguna en absoluto. Crear una clase que sea solo "parcialmente" segura para subprocesos es, IMO, peligroso.

En las palabras de un famoso maestro jedi:

Ya sea que lo haga o no, no hay intento.

Lo mismo aplica para seguridad de hilos. La clase debe ser segura o no. Tomando este enfoque, se queda con el uso de mi aumento de Attempt 5, o con Attempt 7. Dada la opción, nunca recomendaría # 7.

Entonces mi recomendación se mantiene firmemente detrás de una versión completamente segura para subprocesos. El costo de rendimiento entre los dos es tan infinitesimalmente pequeño que casi no existe. Los hilos del lector nunca tocarán la cerradura simplemente por su escenario de uso de tener un solo escritor. Sin embargo, si lo hacen, el comportamiento correcto sigue siendo una certeza. Por lo tanto, a medida que su código cambia con el tiempo y repentinamente su instancia se comparte antes de ser congelada, no termina con la condición de raza que bloquea su programa. Enlazar con seguridad, o no, no estar a medio camino o terminará con desagradable sorpresa algún día.

Mi preferencia es que todas las clases compartidas por más de un hilo sean de dos tipos:

  1. Completamente inmutable.
  2. Completamente seguro para el hilo.

Como una lista de paletas no es inmutable por diseño, no cabe en el # 1. Por lo tanto, si va a compartir el objeto a través de los hilos, debería ajustarse a # 2.

Espero que todo este despotricamiento explique mi razonamiento :)

_syncRoot

Muchas personas han notado que _syncRoot el uso de _syncRoot en mi implementación de bloqueo. Si bien las razones para usar _syncRoot son válidas, no siempre son necesarias. En el ejemplo de uso donde tiene un solo escritor, el uso de lock(this) debería ser suficiente sin agregar otra asignación de montón para _syncRoot .

Estoy diseñando una clase que deseo hacer solo después de que un hilo principal termine de configurarlo, es decir, "congelarlo". Eric Lippert llama inmutabilidad a esta popsicle . Después de que se congela, se puede acceder por varios hilos al mismo tiempo para leer.

Mi pregunta es cómo escribir esto de una manera segura y segura que sea realistamente eficiente, es decir, sin tratar de ser innecesariamente inteligente .

Intento 1:

public class Foobar { private Boolean _isFrozen; public void Freeze() { _isFrozen = true; } // Only intended to be called by main thread, so checks if class is frozen. If it is the operation is invalid. public void WriteValue(Object val) { if (_isFrozen) throw new InvalidOperationException(); // write ... } public Object ReadSomething() { return it; } }

Eric Lippert parece sugerir que esto estaría bien en popsicle publicación. Sé que las escrituras tienen una semántica de lanzamiento, pero por lo que yo sé, esto solo se relaciona con el orden , y no necesariamente significa que todos los hilos verán el valor inmediatamente después de la escritura. ¿Alguien puede confirmar esto? Esto significaría que esta solución no es segura para subprocesos (esta puede no ser la única razón, por supuesto).

Intento 2:

Lo anterior, pero utilizando Interlocked.Exchange para garantizar que el valor se publique realmente:

public class Foobar { private Int32 _isFrozen; public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); } public void WriteValue(Object val) { if (_isFrozen == 1) throw new InvalidOperationException(); // write ... } }

La ventaja aquí sería que nos aseguramos de que el valor se publique sin sufrir los gastos generales en cada lectura. Si ninguna de las lecturas se mueve antes de escribir en _isFrozen, ya que el método de enclavamiento utiliza una barrera de memoria completa, creo que esto es seguro para subprocesos. Sin embargo, quién sabe qué hará el compilador (y de acuerdo con la sección 3.10 de la especificación de C # que parece bastante), así que no sé si esto es seguro o no.

Intento 3:

También haga la lectura usando Interlocked .

public class Foobar { private Int32 _isFrozen; public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); } public void WriteValue(Object val) { if (Interlocked.CompareExchange(ref _isFrozen, 0, 0) == 1) throw new InvalidOperationException(); // write ... } }

Definitivamente es seguro, pero parece un desperdicio tener que hacer el intercambio de comparación para cada lectura. Sé que esta sobrecarga es probablemente mínima, pero estoy buscando un método razonablemente eficiente (aunque tal vez sea éste).

Intento 4:

Usando volatile :

public class Foobar { private volatile Boolean _isFrozen; public void Freeze() { _isFrozen = true; } public void WriteValue(Object val) { if (_isFrozen) throw new InvalidOperationException(); // write ... } }

Pero Joe Duffy declaró " sayonara volátil ", por lo que no consideraré esto como una solución.

Intento 5:

Bloquee todo, parece un poco excesivo:

public class Foobar { private readonly Object _syncRoot = new Object(); private Boolean _isFrozen; public void Freeze() { lock(_syncRoot) _isFrozen = true; } public void WriteValue(Object val) { lock(_syncRoot) // as above we could include an attempt that reads *without* this lock if (_isFrozen) throw new InvalidOperationException(); // write ... } }

También parece definitivamente seguro para subprocesos, pero tiene más sobrecarga que el enfoque Interlocked anterior, por lo que preferiría el intento 3 sobre este.

Y luego puedo encontrar al menos un poco más (estoy seguro de que hay muchos más):

Intento 6: use Thread.VolatileWrite y Thread.VolatileRead , pero supuestamente son un poco pesados.

Intento 7: utilizar Thread.MemoryBarrier , parece un poco demasiado interno .

Intento 8: crear una copia inmutable: no quiero hacer esto

Resumiendo:

  • ¿Qué intento usarías y por qué (o cómo lo harías si fuera completamente diferente)? (es decir, ¿cuál es la mejor manera de publicar un valor una vez que se lee simultáneamente, mientras que es razonablemente eficiente sin ser demasiado "inteligente"?)
  • ¿La semántica de "liberación" del modelo de memoria de .NET de las escrituras implica que todos los demás hilos ven actualizaciones (coherencia de caché, etc.)? Por lo general, no quiero pensar demasiado sobre esto, pero es bueno tener un entendimiento.

EDITAR:

Tal vez mi pregunta no era clara, pero estoy buscando en particular por qué los intentos anteriores son buenos o malos. Tenga en cuenta que estoy hablando aquí sobre un escenario de un solo escritor que escribe y luego se congela antes de cualquier lectura simultánea. Creo que el intento 1 está bien, pero me gustaría saber exactamente por qué (ya que me pregunto si las lecturas podrían optimizarse de alguna manera, por ejemplo). Me importa menos si esta es una buena práctica de diseño o no, pero más sobre el aspecto real de la misma.

Muchas gracias por la respuesta que recibió la pregunta, pero he decidido marcar esto como una respuesta porque siento que las respuestas dadas no responden exactamente a mi pregunta y no quiero dar la impresión a nadie que visite el sitio que el marcado la respuesta es correcta simplemente porque se marcó automáticamente como tal debido a la expiración de la recompensa. Además, no creo que se haya votado abrumadoramente la respuesta con el mayor número de votos, no lo suficiente como para marcarla automáticamente como respuesta.

Todavía me inclino a intentar que el # 1 sea correcto, sin embargo, me han gustado algunas respuestas autorizadas. Entiendo que x86 tiene un modelo fuerte, pero no quiero (y no debería) codificar para una arquitectura en particular, después de todo, esa es una de las cosas buenas de .NET.

Si tiene dudas sobre la respuesta, opte por uno de los enfoques de bloqueo, quizás con las optimizaciones que se muestran aquí para evitar una gran cantidad de disputas en el bloqueo.


# 1 - El lector no es inseguro - Creo que el problema estaría en el lado del lector, no en el escritor (no se muestra el código)
# 2 - lector no enhebrable - igual que # 1
# 3: prometedor, la verificación de lectura se puede optimizar en la mayoría de los casos (cuando las memorias caché de la CPU están sincronizadas)

Intento 3:

También haga la lectura usando Enclavamiento.

public class Foobar { private object _syncRoot = new object(); private int _isFrozen = 0; // perf compiler warning, but training code, so show defaults // Why Exchange to 1 then throw away result. Best to just increment. //public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); } public void Freeze() { Interlocked.Increment(ref _isFrozen); } public void WriteValue(Object val) { // if this core can see _isFrozen then no special lock or sync needed if (_isFrozen != 0) throw new InvalidOperationException(); lock(_syncRoot) { if (_isFrozen != 0) throw new InvalidOperationException(); // the ''throw'' is 100x-1000x more costly than the lock, just eat it _val = val; } } public object Read() { // frozen is one-way, if one-way state has been published // to my local CPU cache then just read _val. // There are very strange corner cases when _isFrozen and _val fields are in // different cache lines, but should be nearly impossible to hit unless // dealing with very large structs (make it more likely to cross // 4k cache line). if (_isFrozen != 0) return _val; // else lock(_syncRoot) { // _isFrozen is 0 here if (_isFrozen != 0) // if _isFrozen is 1 here we just collided with writer using lock on other thread, or our CPU cache was out of sync and lock() forced the dirty cache line to be read from main memory return _val; throw new InvalidOperationException(); // throw is 100x-1000x more expensive than lock, eat the cost of lock } } }

La publicación de Joe Duffy sobre '''' volátil está muerto '''' es, creo, en el contexto de su arquitectura CLR / OS de próxima generación y para CLR en ARM. Quienes hacemos multi-core x64 / x86 creo que volátil está bien. Si perf es la principal preocupación, le sugiero que mida el código anterior y lo compare con volátil.

A diferencia de otras personas que publican respuestas, yo no saltaría directamente a lock () si tienes muchos lectores (3 o más hilos probablemente leerán el mismo objeto al mismo tiempo). Pero en su muestra usted mezcla preguntas perf-sensitive con excepciones cuando ocurre una colisión, lo que no tiene mucho sentido. Si usa excepciones, también puede usar otras construcciones de alto nivel.

Si desea seguridad completa pero necesita optimizar para muchos lectores simultáneos, cambie lock () / Monitor a ReaderWriterLockSlim.

.NET tiene nuevas primitivas para manejar los valores de publicación. Echa un vistazo a Rx. Puede ser muy rápido y sin cerraduras en algunos casos (creo que usan optimizaciones similares a las anteriores).

Si se escribe varias veces, pero solo se conserva un valor, en Rx que es "new ReplaySubject (bufferSize: 1)". Si lo intentas, te sorprenderá cuán rápido. Al mismo tiempo, aplaudo su intento de aprender este nivel de detalle.

Si quieres quedarte sin cerradura, supera tu desagrado por Thread.MemoryBarrier (). Es extremadamente importante Pero tiene los mismos inconvenientes que los descritos por Joe Duffy: fue diseñado como una sugerencia para el compilador y la CPU para evitar el reordenamiento de las lecturas de memoria (que tardan mucho tiempo en términos de CPU, por lo que se reordenan agresivamente cuando no hay consejos presentes). Cuando este reordenamiento se combina con construcciones CLR como auto-inline of functions y se puede ver un comportamiento muy sorprendente en el nivel de memoria y registro. MemoryBarrier () simplemente desactiva las suposiciones de acceso a la memoria de un solo subproceso que la CPU y CLR utilizan la mayor parte del tiempo.


¿Se construye y se escribe, se congela y se lee de forma permanente varias veces?

¿O lo congela y descongela y lo vuelve a congelar varias veces?

Si es el primero, entonces tal vez la verificación "está congelado" debe estar en el método del lector, no en el método del escritor (para evitar que se lea antes de que se congele).

O, si es el último, entonces el caso de uso del que debe tener cuidado es:

  • El hilo principal invoca el método del escritor, descubre que no está congelado y, por lo tanto, comienza a escribir
  • Antes de que la escritura haya terminado, alguien intenta congelar el objeto y luego lee de él, mientras que el otro hilo (principal) sigue escribiendo

En este último caso, Google muestra una gran cantidad de resultados para varios lectores de un solo escritor que puede ser interesante.


El intento 2 es seguro para subprocesos en x86 y otros procesadores que tienen un modelo de memoria fuerte, pero la forma en que lo haría es hacer que la seguridad del subproceso sea un problema para los consumidores porque no hay forma de que pueda hacerlo eficientemente dentro del código consumido. Considerar:

if(!foo.frozen) { foo.apropery = "avalue"; }

El hilo de seguridad de la propiedad frozen y el código de protección en el setter de apropery realmente no importa porque incluso si son perfectamente seguros para hilos, todavía tienes una condición de carrera. En cambio, lo escribiría como

lock(foo) { if(!foo.frozen) { foo.apropery = "avalue"; } }

y no tienen ninguna de las propiedades intrínsecamente seguras.


En general, cada objeto mutable debe tener precisamente un "propietario" claramente definido; los objetos compartidos deben ser inmutables. Las paletas heladas no deben ser accesibles por múltiples hilos hasta que se congelen.

Personalmente, no me gustan las formas de inmunidad a paletas con un método expuesto de "congelación". Creo que un enfoque más limpio es tener métodos AsMutable y AsImmutable (cada uno de los cuales simplemente devolvería el objeto sin modificaciones cuando corresponda). Tal enfoque puede permitir promesas más robustas sobre la inmutabilidad. Por ejemplo, si un "objeto mutable no compartido" está siendo mutado mientras se está AsImmutable su miembro AsImmutable (comportamiento que sería contrario al objeto "no compartido"), el estado de los datos en la copia puede ser indeterminado, pero cualquiera que sea devuelto sería inmutable. Por el contrario, si un hilo congeló un objeto y luego asumió que era inmutable mientras otro hilo estaba escribiendo en él, el objeto "inmutable" podría terminar cambiando después de que se congelara y se leyeran sus valores.

Editar

En base a una descripción más detallada, sugeriría que el código que se escribe en el objeto lo haga dentro de un bloqueo de monitor y que la rutina de congelación tenga un aspecto similar a:

public Thingie Freeze(void) // Returns the object in question { if (isFrozen) // Private field return this; else return DoFreeze(); } Thingie DoFreeze(void) { if (Monitor.TryEnter(whatever)) { isFrozen = true; return this; } else if (isFrozen) return this; else throw new InvalidOperationException("Object in use by writer"); }

El método Freeze se puede invocar cualquier cantidad de veces por cualquier cantidad de subprocesos; debería ser lo suficientemente corto como para estar en línea (aunque no lo he perfilado), y por lo tanto no debería tomar casi ningún tiempo para ejecutarlo. Si el primer acceso del objeto en cualquier hilo es mediante el método Freeze , eso debería garantizar la visibilidad adecuada bajo cualquier modelo de memoria razonable (incluso si el hilo no vio las actualizaciones del objeto realizadas por el hilo que lo creó y originalmente lo congeló , realizaría el TryEnter , que garantizaría una barrera de memoria, y después de eso fallaría se daría cuenta de que el objeto estaba congelado y lo devolvería.

Si el código que va a escribir el objeto adquiere primero el bloqueo, un intento de escribir en un objeto congelado podría bloquearlo. Si uno prefiere que dicho código arroje una excepción, uno usa TryEnter y lanza una excepción si no puede obtener el bloqueo.

El objeto utilizado para el bloqueo debe ser algo que se sostiene exclusivamente por el objeto que se congelará. Si el objeto a congelar no contiene una referencia puramente privada a nada, uno podría bloquearlo o crear un objeto privado exclusivamente con fines de bloqueo. Tenga en cuenta que es seguro abandonar bloqueos de monitor ''ingresados'' sin limpieza; el GC simplemente se olvidará de ellos, ya que si no existen referencias a un candado, no hay forma de que a alguien le importe (o incluso podría preguntar) si el candado se ingresó en el momento en que fue abandonado.


No estoy seguro de cómo va a ser el siguiente enfoque, pero es un poco diferente. Solo inicialmente, si hay varios hilos que intentan escribir valor simultáneamente, se encontrarán con bloqueos. Una vez que se congela, todas las llamadas posteriores recibirán la excepción directamente.

Intento 9:

public class Foobar { private readonly Object _syncRoot = new Object(); private object _val; private Boolean _isFrozen; private Action<object> WriteValInternal; public void Freeze() { _isFrozen = true; } public Foobar() { WriteValInternal = BeforeFreeze; } private void BeforeFreeze(object val) { lock (_syncRoot) { if (_isFrozen == false) { //Write the values.... _val = val; //... //... //... //and then modify the write value function WriteValInternal = AfterFreeze; Freeze(); } else { throw new InvalidOperationException(); } } } private void AfterFreeze(object val) { throw new InvalidOperationException(); } public void WriteValue(Object val) { WriteValInternal(val); } public Object ReadSomething() { return _val; } }


Si realmente creas, llenas y congelas el objeto antes de mostrárselo a otros hilos, entonces no necesitas nada especial para manejar la seguridad de hilos (el fuerte modelo de memoria de .NET ya es tu garantía), entonces la solución 1 es válido.

Pero, si le das el objeto no congelado a otro hilo (o si eres simple creando tu clase sin saber cómo lo usarán los usuarios), entonces el uso de la versión es probablemente la mejor solución que devuelve una nueva instancia completamente inmutable. En este caso, la instancia de Mutable es como StringBuilder y la instancia inmutable es como la cadena. Si necesita una garantía adicional, la instancia mutable puede verificar su hilo creador y lanzar excepciones si se usa desde cualquier otro hilo (en todos los métodos ... para evitar posibles lecturas parciales).


Tal vez un poco fuera de tema, pero solo por curiosidad :) ¿Por qué no usas la inmutabilidad "real"? por ejemplo, hacer que Freeze () devuelva una copia inmutable (sin "métodos de escritura" o cualquier otra posibilidad de cambiar el estado interno) y usar esta copia en lugar del objeto original. Incluso podría ir sin cambiar el estado y devolver una nueva copia (con el estado modificado) en cada operación de escritura (afaik la clase de cadena funciona así). La "inmutabilidad real" es inherentemente segura para hilos.


Voto por el intento 5, uso la implementación de bloqueo (esto).

Este es el medio más confiable para hacer que esto funcione. Se pueden emplear bloqueos de lector / escritor, pero con muy poca ganancia. Solo usa un bloqueo normal.

Si es necesario, puede mejorar el rendimiento "congelado" comprobando primero _isFrozen y luego bloqueando:

void Freeze() { lock (this) _isFrozen = true; } object ReadValue() { if (_isFrozen) return Read(); else lock (this) return Read(); } void WriteValue(object value) { lock (this) { if (_isFrozen) throw new InvalidOperationException(); Write(value); } }



you may achieve this using POST Sharp

take one interface

public interface IPseudoImmutable { bool IsFrozen { get; } bool Freeze(); }

then derive your attribute from InstanceLevelAspect like this

/// <summary> /// implement by divyang /// </summary> [Serializable] [IntroduceInterface(typeof(IPseudoImmutable), AncestorOverrideAction = InterfaceOverrideAction.Ignore, OverrideAction = InterfaceOverrideAction.Fail)] public class PseudoImmutableAttribute : InstanceLevelAspect, IPseudoImmutable { private volatile bool isFrozen; #region "IPseudoImmutable" [IntroduceMember] public bool IsFrozen { get { return this.isFrozen; } } [IntroduceMember(IsVirtual = true, OverrideAction = MemberOverrideAction.Fail)] public bool Freeze() { if (!this.isFrozen) { this.isFrozen = true; } return this.IsFrozen; } #endregion [OnLocationSetValueAdvice] [MulticastPointcut(Targets = MulticastTargets.Property | MulticastTargets.Field)] public void OnValueChange(LocationInterceptionArgs args) { if (!this.IsFrozen) { args.ProceedSetValue(); } } } public class ImmutableException : Exception { /// <summary> /// The location name. /// </summary> private readonly string locationName; /// <summary> /// Initializes a new instance of the <see cref="ImmutableException"/> class. /// </summary> /// <param name="message"> /// The message. /// </param> public ImmutableException(string message) : base(message) { } public ImmutableException(string message, string locationName) : base(message) { this.locationName = locationName; } public string LocationName { get { return this.locationName; } } }

then apply in your class like this

[PseudoImmutableAttribute] public class TestClass { public string MyString { get; set; } public int MyInitval { get; set; } }

luego ejecutarlo en multi thread

/// <summary> /// The program. /// </summary> public class Program { /// <summary> /// The main. /// </summary> /// <param name="args"> /// The args. /// </param> public static void Main(string[] args) { Console.Title = "Divyang Demo "; var w = new Worker(); w.Run(); Console.ReadLine(); } } internal class Worker { private object SyncObject = new object(); public Worker() { var r = new Random(); this.ObjectOfMyTestClass = new MyTestClass { MyInitval = r.Next(500) }; } public MyTestClass ObjectOfMyTestClass { get; set; } public void Run() { Task readWork; readWork = Task.Factory.StartNew( action: () => { for (;;) { Task.Delay(1000); try { this.DoReadWork(); } catch (Exception exception) { // Console.SetCursorPosition(80,80); // Console.SetBufferSize(100,100); Console.WriteLine("Read Exception : {0}", exception.Message); } } // ReSharper disable FunctionNeverReturns }); Task writeWork; writeWork = Task.Factory.StartNew( action: () => { for (int i = 0; i < int.MaxValue; i++) { Task.Delay(1000); try { this.DoWriteWork(); } catch (Exception exception) { Console.SetCursorPosition(80, 80); Console.SetBufferSize(100, 100); Console.WriteLine("write Exception : {0}", exception.Message); } if (i == 5000) { ((IPseudoImmutable)this.ObjectOfMyTestClass).Freeze(); } } }); Task.WaitAll(); } /// <summary> /// The do read work. /// </summary> public void DoReadWork() { // ThreadId where reading is done var threadId = System.Threading.Thread.CurrentThread.ManagedThreadId; // printing on screen lock (this.SyncObject) { Console.SetCursorPosition(0, 0); Console.SetBufferSize(290, 290); Console.WriteLine("/n"); Console.WriteLine("Read Start"); Console.WriteLine("Read => Thread Id: {0} ", threadId); Console.WriteLine("Read => this.objectOfMyTestClass.MyInitval: {0} ", this.ObjectOfMyTestClass.MyInitval); Console.WriteLine("Read => this.objectOfMyTestClass.MyString: {0} ", this.ObjectOfMyTestClass.MyString); Console.WriteLine("Read End"); Console.WriteLine("/n"); } } /// <summary> /// The do write work. /// </summary> public void DoWriteWork() { // ThreadId where reading is done var threadId = System.Threading.Thread.CurrentThread.ManagedThreadId; // random number generator var r = new Random(); var count = r.Next(15); // new value for Int property var tempInt = r.Next(5000); this.ObjectOfMyTestClass.MyInitval = tempInt; // new value for string Property var tempString = "Randome" + r.Next(500).ToString(CultureInfo.InvariantCulture); this.ObjectOfMyTestClass.MyString = tempString; // printing on screen lock (this.SyncObject) { Console.SetBufferSize(290, 290); Console.SetCursorPosition(125, 25); Console.WriteLine("/n"); Console.WriteLine("Write Start"); Console.WriteLine("Write => Thread Id: {0} ", threadId); Console.WriteLine("Write => this.objectOfMyTestClass.MyInitval: {0} and New Value :{1} ", this.ObjectOfMyTestClass.MyInitval, tempInt); Console.WriteLine("Write => this.objectOfMyTestClass.MyString: {0} and New Value :{1} ", this.ObjectOfMyTestClass.MyString, tempString); Console.WriteLine("Write End"); Console.WriteLine("/n"); } } } but still it will allow you to change property like array ,list . but if you apply more login in that then it may work for all type of property and field


Haría algo como esto, inspirado en C ++ tipos móviles. Solo recuerda no acceder al objeto después de Congelar / Descongelar.

Por supuesto, puede agregar un _data != nullcheque / lanzamiento si desea tener claro por qué el usuario obtiene un NRE si accede después de la descongelación / congelación.

public class Data { public string _foo; public int _bar; } public class Mutable { private Data _data = new Data(); public Mutable() {} public string Foo { get => _data._foo; set => _data._foo = value; } public int Bar { get => _data._bar; set => _data._bar = value; } public Frozen Freeze() { var f = new Frozen(_data); _data = null; return f; } } public class Frozen { private Data _data; public Frozen(Data data) => _data = data; public string Foo => _data._foo; public int Bar => _data._bar; public Mutable Thaw() { var m = new Mutable(_data); _data = null; return m; } }