Implementación de cola Rx y búfer de Dispatcher

Quiero implementar una cola que sea capaz de tomar eventos/elementos de múltiples productores en múltiples subprocesos y consumirlos todos en un solo subproceso. esta cola funcionará en algún entorno crítico, por lo que estoy bastante preocupado por su estabilidad.

Lo he implementado usando las capacidades de Rx, pero tengo 2 preguntas:

  1. ¿Está bien esta implementación? ¿O tal vez tiene algún defecto que desconozco? (como alternativa - implementación manual con cola y bloqueos)
  2. ¿Cuál es la longitud del búfer de Dispatcher? ¿Puede manejar 100k de elementos en cola?

El siguiente código ilustra mi enfoque, usando un TestMethod simple. Su salida muestra que todos los valores se ingresan desde diferentes subprocesos, pero se procesan en otro subproceso único.

[TestMethod()]
public void RxTest()
{
    Subject<string> queue = new Subject<string>();

    queue
        .ObserveOnDispatcher()
        .Subscribe(s =>
                        {
                            Debug.WriteLine("Value: {0}, Observed on ThreadId: {1}", s, Thread.CurrentThread.ManagedThreadId);
                        },
                    () => Dispatcher.CurrentDispatcher.InvokeShutdown());

    for (int j = 0; j < 10; j++)
    {
        ThreadPool.QueueUserWorkItem(o =>
        {
            for (int i = 0; i < 100; i++)
            {
                Thread.Sleep(10);
                queue.OnNext(string.Format("value: {0}, from thread: {1}", i.ToString(), Thread.CurrentThread.ManagedThreadId));
            }
            queue.OnCompleted();
        });
    }


    Dispatcher.Run();
}

preguntado el 02 de julio de 12 a las 12:07

2 Respuestas

No estoy seguro del comportamiento de Subject en escenarios con múltiples subprocesos. Sin embargo, me puedo imaginar que algo como BlockingCollection (y su subyacente ConcurrentQueue) están bien usados ​​en las situaciones de las que estás hablando. Y fácil de arrancar.

var queue = new BlockingCollection<long>();

// subscribing
queue.GetConsumingEnumerable()
     .ToObservable(Scheduler.NewThread)
     .Subscribe(i => Debug.WriteLine("Value: {0}, Observed on ThreadId: {1}", i, Thread.CurrentThread.ManagedThreadId));

// sending
Observable.Interval(TimeSpan.FromMilliseconds(500), Scheduler.ThreadPool)
          .Do(i => Debug.WriteLine("Value: {0}, Sent on ThreadId: {1}", i, Thread.CurrentThread.ManagedThreadId))
          .Subscribe(i => queue.Add(i));

Ciertamente no querrás tocar colas y bloqueos. El ConcurrentQueue la implementación es excelente y ciertamente manejará las colas de tamaño de las que está hablando de manera efectiva.

Respondido 02 Jul 12, 12:07

Muchas gracias, su ejemplo es muy bueno y funciona exactamente como lo necesito sin tener que jugar con Dispatcher. - oveja sorda

Aunque, creo que hay un problema en su implementación: ¿qué pasa si esta cola funciona durante un mes en vivo? Almacenará todos los artículos en él, ¿verdad? ¿Significa que consumirá multitudes en la memoria para contener todo eso? - oveja sorda

Los elementos de @deafsheep se están agotando de la cola, por lo que no los almacenará todos a menos que no se consuman, que es el comportamiento que desea. - yamen

@yamen, por lo que si tuviera que agregar otro consumidor, tendría que obtener otro enumerable de consumo y cambiarlo a observable, ¿verdad? ¿Y para eliminar al consumidor solo dispongo de la suscripción? - Krzysztof Skowronek

Eche un vistazo a EventLoopScheduler. Está integrado en RX y creo que hace todo lo que quieres.

Puede tomar cualquier número de observables, llame .ObserveOn(els) (els es tu instancia de un EventLoopScheduler) y ahora está organizando múltiples observables de múltiples subprocesos en un solo subproceso y poniendo en cola cada llamada para OnNext en serie.

Respondido 02 Jul 12, 12:07

EventLoopScheduler podría ser mi elección, pero ¿es posible "ejecutar" ese programador en el hilo actual? ¿En la forma de bloqueo de Dispatcher's Run? - oveja sorda

@deafsheep EventLoopScheduler ejecuta en serie el trabajo en diferentes subprocesos para cada elemento. El CurrentThreadScheduler ejecuta código en el subproceso actual (que es automáticamente en serie porque un subproceso solo puede hacer una cosa a la vez). También genera situaciones de interbloqueo más rápido de lo que puede escribir si lo usa mal. Para la situación que describió, Vanilla EventLoopScheduler es probablemente el camino a seguir. - anderson imes

@AndersonImes - El EventLoopScheduler solo usa un hilo nuevo si no hay elementos pendientes. Si hay un número en cola, continúa usando el mismo hilo. - Enigmatividad

@Enigmativity ah, tienes razón, esa es una distinción importante. Parece que hace un EnsureThread() en una nueva llamada programada y una this.thread = null cuando la cola está vacía. De todos modos, la tuya sigue siendo probablemente la recomendación correcta. :) - anderson imes

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.