Para que sus aplicaciones C#/.NET sean rápidas y responsivas, debe equilibrar el tiempo de procesamiento necesario para diseñar los elementos visuales y gestionar las interacciones del usuario con el tiempo necesario para procesar los datos y ejecutar la lógica de negocio. Normalmente, los desarrolladores de aplicaciones distribuyen este trabajo entre varios subprocesos: el subproceso principal o de interfaz de usuario para todo el trabajo relacionado con la interfaz de usuario, y uno o más subprocesos en segundo plano para procesar las cargas de trabajo más pesadas antes de enviarlas al subproceso de interfaz de usuario para su presentación. Al delegar el trabajo pesado a los subprocesos en segundo plano, el subproceso de interfaz de usuario puede mantener una alta capacidad de respuesta independientemente del tamaño de la carga de trabajo. Sin embargo, puede ser notoriamente difícil escribir código multiproceso seguro para subprocesos, de alto rendimiento y fácil de mantener que evite problemas como los interbloqueos y las condiciones de carrera. Realm pretende simplificar esto para usted.
Importante
Hilos de SynchronizationContext
En esta página, nos referimos al "hilo principal" (o "hilo de interfaz de usuario") y a los "hilos en segundo plano". Para ser más precisos, cualquier mención del hilo principal o de interfaz de usuario se refiere a cualquier hilo con...
Contexto de sincronización, mientras que aquellos hilos sin un SynchronizationContext se consideran hilos de fondo.
Tres reglas a seguir
Antes de explorar las herramientas de Realm para aplicaciones multiproceso, debes comprender y seguir estas tres reglas:
- No bloquear para leer:
- La arquitectura de Control de Concurrencia Multiversión (MVCC) de Realm elimina la necesidad de bloqueos en las operaciones de lectura. Los valores leídos nunca se corromperán ni se modificarán parcialmente. Se puede leer libremente del mismo archivo de Realm en cualquier hilo sin necesidad de bloqueos ni mutex. Un bloqueo innecesario supondría un cuello de botella en el rendimiento, ya que cada hilo podría tener que esperar su turno antes de leer.
- Evite escrituras sincrónicas en el hilo de UI:
- Puedes escribir en un archivo Realm desde cualquier hilo, pero solo puede haber un escritor a la vez. Las transacciones de escritura síncrona se bloquean entre sí. Por lo tanto, una escritura síncrona en el hilo principal puede provocar que tu aplicación parezca no responder mientras espera a que se complete una escritura en un hilo en segundo plano. Para evitar esto, el SDK proporciona el método WriteAsync(). Para más información, consulta Escrituras asíncronas.
- No pase objetos activos, colecciones o reinos a otros subprocesos:
- Los objetos activos, las colecciones y las instancias de realm están confinados a subprocesos: es decir, solo son válidos en el subproceso en el que se crearon. En la práctica, esto significa que no se pueden pasar instancias activas a otros subprocesos. Sin embargo, Realm ofrece varios mecanismos para compartir objetos entre subprocesos.
Comunicación a través de subprocesos
Para acceder al mismo archivo de dominio desde diferentes subprocesos, debe crear una instancia de dominio en cada subproceso que necesite acceso. Siempre que especifique la misma configuración, todas las instancias de dominio se asignarán al mismo archivo en el disco.
Una de las reglas clave al trabajar con Realm en un entorno multihilo es que los objetos están confinadosa cada hilo: no se puede acceder a las instancias de un reino, colección u objeto originadas en otros hilos. La arquitectura de Control de Concurrencia Multiversión (MVCC) de Realm implica que podría haber varias versiones activas de un objeto en cualquier momento. El confinamiento de hilos garantiza que todas las instancias de ese hilo tengan la misma versión interna.
Cuando necesita comunicarse a través de subprocesos, tiene varias opciones según su caso de uso:
Para modificar un objeto en dos subprocesos, consulte el objeto en ambos subprocesos.
Para reaccionar a los cambios realizados en cualquier hilo, utilice las notificaciones de Realm.
Para ver los cambios que ocurrieron en otro hilo en la instancia de reino del hiloactual, actualice su instancia de reino.
Para enviar una vista rápida y de solo lectura del objeto a otros subprocesos, "congele" el objeto.
Para conservar y compartir muchas vistas de solo lectura del objeto en su aplicación, copie el objeto desde el reino.
Reinos refrescantes
En el subproceso principal de la Interfaz de Usuario (o en cualquier subproceso con un ciclo de ejecución), Realm actualiza automáticamente los objetos al comienzo de cada iteración del ciclo de ejecución. Entre las iteraciones del ciclo de ejecución, se trabajará en la snapshot; así, los métodos individuales siempre ven una vista coherente y nunca tienen que preocuparse por lo que ocurre en otros hilos.
Al abrir un dominio en un hilo, su estado será la última confirmación de escritura correcta y permanecerá en esa versión hasta que se actualice. Si un hilo no tiene un bucle de ejecución (lo que suele ocurrir en un hilo en segundo plano), se debe llamar manualmente al método Realm.Refresh() para avanzar la transacción al estado más reciente.
Los reinos también se actualizan cuando se confirman transacciones de escritura con Transaction.Commit().
Nota
Si no se actualizan Realms periódicamente, es posible que algunas versiones de transacciones queden "fijadas", lo que impide que Realm reutilice el espacio en disco utilizado por esa versión, lo que genera tamaños de archivo más grandes.
Escrituras asincrónicas
El método WriteAsync() proporciona una forma sencilla de descargar el hilo de la interfaz de usuario. Realm iniciará y confirmará la transacción de forma asíncrona, pero el bloque de escritura se ejecutará en el hilo original. Por lo tanto, la espera de cambios es asíncrona, pero la devolución de llamada se ejecuta en el hilo principal. Esto significa que los objetos y las consultas creadas antes del bloque de escritura se pueden usar dentro del bloque sin depender de referencias seguras para subprocesos.
El siguiente código muestra dos ejemplos de creación de un objeto con AsyncWrite().
var testItem = new Item { Name = "Do this thing", Status = ItemStatus.Open.ToString(), Assignee = "Aimee" }; await realm.WriteAsync(() => { realm.Add(testItem); }); // Or var testItem2 = await realm.WriteAsync(() => { return realm.Add<Item>(new Item { Name = "Do this thing, too", Status = ItemStatus.InProgress.ToString(), Assignee = "Satya" }); } );
Nota
Si llama WriteAsync() a en un hilo en segundo plano, Realm se ejecuta sincrónicamente en el hilo, por lo que es el equivalente a llamar a Write().
Objetos congelados
Los objetos activos y confinados en subprocesos funcionan correctamente en la mayoría de los casos. Sin embargo, algunas aplicaciones, como las basadas en arquitecturas reactivas basadas en flujos de eventos, necesitan enviar copias inmutables a varios subprocesos para su procesamiento antes de llegar al subproceso de la interfaz de usuario. Realizar una copia profunda cada vez sería costoso, y Realm no permite compartir instancias activas entre subprocesos. En este caso, se pueden congelar objetos, colecciones y reinos.
La congelación crea una vista inmutable de un objeto, colección o reino específico que aún existe en el disco y no necesita copiarse en profundidad al pasarse a otros subprocesos. Puedes compartir libremente un objeto congelado entre subprocesos sin preocuparte por problemas.
Al trabajar con objetos congelados, cualquier intento de realizar cualquiera de las siguientes acciones genera una excepción:
Abriendo una transacción de escritura en un reino congelado.
Modificar un objeto congelado.
Agregar un detector de cambios a un reino, una colección o un objeto congelado.
Una vez congelado, no es posible descongelar un objeto. Puedes usar el método IsFrozen para comprobar si el objeto está congelado. Este método siempre es seguro para subprocesos.
Para modificar un objeto congelado, consúltelo en un reino no congelado y luego modifíquelo.
Los objetos congelados no están activos ni se actualizan automáticamente. Son, en realidad, instantáneas del estado del objeto en el momento de la congelación.
Cuando congelas un reino, sus objetos secundarios también se congelan.
Los objetos congelados permanecen válidos siempre que el realm vivo que los generó permanezca abierto. Por lo tanto, evite cerrar el realm activo hasta que todos los hilos hayan terminado con los objetos congelados. Puede cerrar el realm congelado antes de que se cierre el realm activo.
Importante
Sobre el almacenamiento en caché de objetos congelados
Almacenar en caché demasiados objetos congelados puede tener un impacto negativo en el tamaño del archivo Realm. "Demasiados" depende de tu dispositivo objetivo específico y el tamaño de tus objetos Realm. Si necesitas almacenar en caché un gran número de versiones, considera copiar lo que necesitas fuera de realm.
El modelo de subprocesos de Realm en profundidad
Realm proporciona acceso seguro, rápido, sin bloqueos y simultáneo entre subprocesos con su arquitectura de control de concurrencia multiversión (MVCC).
Comparado y contrastado con Git
Si está familiarizado con un sistema de control de versiones distribuido como Git, es posible que ya tenga una comprensión intuitiva de MVCC. Dos elementos fundamentales de Git son:
Confirmaciones, que son escrituras atómicas.
Ramas, que son diferentes versiones del historial de confirmaciones.
De forma similar, Realm cuenta con escrituras confirmadas atómicamente en forma de transacciones. Realm también cuenta con múltiples versiones del historial en un momento dado, como las ramas.
A diferencia de Git, que apoya activamente la distribución y la divergencia mediante bifurcaciones, un reino solo tiene una versión más reciente en un momento dado y siempre escribe en la cabecera de esa última versión. Un reino no puede escribir en una versión anterior. Esto tiene sentido: tus datos deberían converger en una versión más reciente de la verdad.
Estructura interna
Un reino se implementa mediante una estructura de datos de árbol B+. El nodo de nivel superior representa una versión del reino; los nodos secundarios son objetos de esa versión. El reino tiene un puntero a su última versión, similar a cómo Git tiene un puntero a su confirmación HEAD.
Realm utiliza una técnica de copia en escritura para garantizar el aislamiento y la durabilidad. Al realizar cambios, Realm copia la parte relevante del árbol para su escritura. A continuación, Realm confirma los cambios en dos fases:
Realm escribe los cambios en el disco y verifica el éxito.
Luego, Realm establece su puntero de última versión para apuntar a la versión recién escrita.
Este proceso de confirmación en dos pasos garantiza que, incluso si la escritura falla a mitad de camino, la versión original no se corrompa, ya que los cambios se realizaron en una copia de la parte relevante del árbol. Asimismo, el puntero raíz del dominio apuntará a la versión original hasta que se garantice la validez de la nueva versión.
Ejemplo
El siguiente diagrama ilustra el proceso de confirmación:

El dominio está estructurado como un árbol. Tiene un puntero a su última versión, V1.
Cuando se realiza una escritura, Realm crea una nueva versión V2 basada en V1. Realm crea copias de objetos para su modificación (A 1, C 1), mientras que los enlaces a objetos no modificados siguen apuntando a las versiones originales (B, D).
Después de validar la confirmación, Realm actualiza el puntero a la nueva versión más reciente, V2. A continuación, Realm descarta los nodos antiguos que ya no están conectados al árbol.
Realm utiliza técnicas de copia cero, como el mapeo de memoria, para gestionar los datos. Al leer un valor del realm, prácticamente se ve el valor en el disco real, no una copia. Esta es la base de los objetos activos. Por eso también se puede configurar un puntero principal del realm para que apunte a la nueva versión tras validar la escritura en el disco.
Resumen
Realm permite un código multiproceso simple y seguro cuando se siguen tres reglas:
No bloquees para leer
Eviteescrituras sincrónicas en el hilo de la interfaz de usuario si escribe en hilos en segundo plano o usa la sincronización del dispositivo
No pase objetos vivos a otros subprocesos.
Existe una forma adecuada de compartir objetos entre subprocesos para cada caso de uso.
Para ver los cambios realizados en otros subprocesos de su instancia de reino, debe actualizar manualmente las instancias de reino que no existen en subprocesos de "bucle" o que tienen la actualización automática deshabilitada.
Para las aplicaciones basadas en arquitecturas reactivas basadas en flujo de eventos, puede congelar objetos, colecciones y reinos para pasar copias superficiales de manera eficiente a diferentes subprocesos para su procesamiento.
La arquitectura de control de concurrencia multiversión (MVCC) de Realm es similar a la de Git. A diferencia de Git, Realm solo tiene una versión actualizada para cada reino.
Realm realiza compromisos en dos etapas para garantizar el aislamiento y la durabilidad.