Compartir a través de


Uso de programadores

Un programador controla cuándo se inicia una suscripción y cuándo se publican las notificaciones. Consta de tres componentes. Primero es una estructura de datos. Cuando se programan las tareas que se van a completar, se colocan en el programador para la puesta en cola en función de la prioridad u otros criterios. También ofrece un contexto de ejecución que indica dónde se ejecuta la tarea (por ejemplo, en el grupo de subprocesos, el subproceso actual o en otro dominio de aplicación). Por último, tiene un reloj que proporciona una noción de tiempo para sí mismo (accediendo a la Now propiedad de un programador). Las tareas que se programan en un programador determinado se adhieren a la hora indicada solo por ese reloj.

Los programadores también presentan la noción de tiempo virtual (indicado por el tipo VirtualScheduler), que no se correlaciona con el tiempo real que se usa en nuestra vida diaria. Por ejemplo, una secuencia que se especifica para tardar 100 años en completarse se puede programar para completarse en tiempo virtual en tan solo 5 minutos. Esto se tratará en el tema Pruebas y depuración de secuencias observables .

Tipos de programador

Los distintos tipos de Scheduler proporcionados por Rx implementan la interfaz IScheduler . Cada uno de estos se puede crear y devolver mediante propiedades estáticas del tipo Scheduler. ImmediateScheduler (accediendo a la propiedad Immediate estática) iniciará la acción especificada inmediatamente. CurrentThreadScheduler (accediendo a la propiedad CurrentThread estática) programará las acciones que se realizarán en el subproceso que realiza la llamada original. La acción no se ejecuta inmediatamente, pero se coloca en una cola y solo se ejecuta una vez completada la acción actual. DispatcherScheduler (mediante el acceso a la propiedad static Dispatcher) programará acciones en el distribuidor actual, lo que es beneficioso para los desarrolladores de Silverlight que usan Rx. A continuación, las acciones especificadas se delegan al método Dispatcher.BeginInvoke() en Silverlight. NewThreadScheduler (accediendo a la propiedad NewThread estática) programa acciones en un subproceso nuevo y es óptimo para programar acciones de larga duración o bloqueo. TaskPoolScheduler (accediendo a la propiedad TaskPool estática) programa acciones en un generador de tareas específico. ThreadPoolScheduler (accediendo a la propiedad ThreadPool estática) programa acciones en el grupo de subprocesos. Ambos programadores de grupo están optimizados para acciones de ejecución corta.

Uso de programadores

Es posible que ya haya usado programadores en el código Rx sin indicar explícitamente el tipo de programadores que se van a usar. Esto se debe a que todos los operadores observables que tratan con la simultaneidad tienen varias sobrecargas. Si no usa la sobrecarga que toma un programador como argumento, Rx elegirá un programador predeterminado mediante el principio de simultaneidad mínima. Esto significa que se elige el programador que introduce la menor cantidad de simultaneidad que satisface las necesidades del operador.  Por ejemplo, para los operadores que devuelven un observable con un número finito y pequeño de mensajes, Rx llama a Immediate.  Para los operadores que devuelven un número potencialmente grande o infinito de mensajes, se llama a CurrentThread . Para los operadores que usan temporizadores, se usa ThreadPool .

Dado que Rx usa el programador de simultaneidad mínima, puede elegir otro programador si desea introducir simultaneidad con fines de rendimiento o cuando tenga un problema de afinidad de subprocesos.  Un ejemplo del anterior es que cuando no desea bloquear un subproceso determinado, en este caso, debe usar ThreadPool.  Un ejemplo de este último es que, cuando se desea que se ejecute un temporizador en la interfaz de usuario, en este caso, debe usar Dispatcher. Para especificar un programador determinado, puede usar esas sobrecargas de operador que toman un programador, por ejemplo, Timer(TimeSpan.FromSeconds(10), Scheduler.DispatcherScheduler()).

En el ejemplo siguiente, la secuencia observable de origen genera valores a un ritmo frenético. La sobrecarga predeterminada del operador Timer colocaría los mensajes OnNext en ThreadPool.

Observable.Timer(Timespan.FromSeconds(0.01))
          .Subscribe(…);

Esto se pondrá en cola rápidamente en el observador. Podemos mejorar este código mediante el operador ObserveOn, que le permite especificar el contexto que desea usar para enviar notificaciones push (OnNext) a los observadores. De forma predeterminada, el operador ObserveOn garantiza que se llamará a OnNext tantas veces como sea posible en el subproceso actual. Puede usar sus sobrecargas y redirigir las salidas OnNext a un contexto diferente. Además, puede usar el operador SubscribeOn para devolver un proxy observable que delegue las acciones a un programador específico. Por ejemplo, para una aplicación que consume mucha interfaz de usuario, puede delegar todas las operaciones en segundo plano que se realizarán en un programador que se ejecute en segundo plano mediante SubscribeOn y pasarla a threadPoolScheduler. Para recibir notificaciones que se insertan y acceden a cualquier elemento de interfaz de usuario, puede pasar una instancia de DispatcherScheduler al operador ObserveOn.

En el ejemplo siguiente se programarán las notificaciones OnNext en el distribuidor actual, de modo que cualquier valor insertado se envíe en el subproceso de la interfaz de usuario. Esto es especialmente beneficioso para los desarrolladores de Silverlight que usan Rx.

Observable.Timer(Timespan.FromSeconds(0.01))
          .ObserveOn(Scheduler.DispatcherScheduler)
          .Subscribe(…);

En lugar de usar el operador ObserveOn para cambiar el contexto de ejecución en el que la secuencia observable genera mensajes, podemos crear simultaneidad en el lugar correcto con el que empezar. A medida que los operadores parametrizan la introducción de la simultaneidad proporcionando una sobrecarga de argumentos del programador, pasar el programador correcto dará lugar a menos lugares donde se debe usar el operador ObserveOn. Por ejemplo, podemos desbloquear el observador y suscribirse directamente al subproceso de interfaz de usuario cambiando el programador usado por el origen, como en el ejemplo siguiente. En este código, mediante la sobrecarga del temporizador que toma un programador y proporciona la Scheduler.Dispatcher instancia, todos los valores insertados desde esta secuencia observable se originarán en el subproceso de la interfaz de usuario.

Observable.Timer(Timespan.FromSeconds(0.01), Scheduler.DispatcherScheduler)
          .Subscribe(…);

También debe tener en cuenta que mediante el operador ObserveOn, se programa una acción para cada mensaje que pasa por la secuencia observable original. Esto puede cambiar la información de control de tiempo, así como también pone estrés adicional en el sistema. Si tiene una consulta que compone varias secuencias observables que se ejecutan en muchos contextos de ejecución diferentes y está filtrando en la consulta, es mejor colocar ObserveOn más adelante en la consulta. Esto se debe a que una consulta podría filtrar una gran cantidad de mensajes y colocar el operador ObserveOn anteriormente en la consulta haría trabajo adicional en los mensajes que se filtrarían de todos modos. Al llamar al operador ObserveOn al final de la consulta, se creará el menor impacto en el rendimiento.

Otra ventaja de especificar explícitamente un tipo de programador es que puede introducir simultaneidad con fines de rendimiento, como se muestra en el código siguiente.

seq.GroupBy(...)
        .Select(x=>x.ObserveOn(Scheduler.NewThread))
        .Select(x=>expensive(x))  // perform operations that are expensive on resources