2010-02-15 28 views
7

¿Hay alguna manera de acceder a un XmlReader de forma asíncrona? El xml viene de la red desde muchos clientes diferentes, como en XMPP; es un flujo constante de etiquetas <action>...</action>.XmlReader asincrónico en .NET?

Lo que busco es poder utilizar una interfaz BeginRead/EndRead-like. La mejor solución que he logrado es hacer una lectura asíncrona de 0 bytes en la transmisión de red subyacente, y cuando llegan algunos datos, llame a Read en el XmlReader; sin embargo, esto bloqueará hasta que todos los datos del nodo se vuelve disponible. Esa solución se ve más o menos así EDITAR

private Stream syncstream; 
private NetworkStream ns; 
private XmlReader reader; 

//this code runs first 
public void Init() 
{ 
    syncstream = Stream.Synchronized(ns); 
    reader = XmlReader.Create(syncstream); 
    byte[] x = new byte[1]; 
    syncstream.BeginRead(x, 0, 0, new AsynchronousCallback(ReadCallback), null); 
} 

private void ReadCallback(IAsyncResult ar) 
{ 
    syncstream.EndRead(ar); 
    reader.Read(); //this will block for a while, until the entire node is available 
    //do soemthing to the xml node 
    byte[] x = new byte[1]; 
    syncstream.BeginRead(x, 0, 0, new AsynchronousCallback(ReadCallback), null); 
} 

: Se trata de un posible algoritmo para la elaboración de si una cadena contiene un nodo XML completo?

Func<string, bool> nodeChecker = currentBuffer => 
       { 
        //if there is nothing, definetly no tag 
        if (currentBuffer == "") return false; 
        //if we have <![CDATA[ and not ]]>, hold on, else pass it on 
        if (currentBuffer.Contains("<![CDATA[") && !currentBuffer.Contains("]]>")) return false; 
        if (currentBuffer.Contains("<![CDATA[") && currentBuffer.Contains("]]>")) return true; 
        //these tag-related things will also catch <? ?> processing instructions 
        //if there is a < but no >, we still have an open tag 
        if (currentBuffer.Contains("<") && !currentBuffer.Contains(">")) return false; 
       //if there is a <...>, we have a complete element. 
       //>...< will never happen because we will pass it on to the parser when we get to > 
       if (currentBuffer.Contains("<") && currentBuffer.Contains(">")) return true; 
       //if there is no < >, we have a complete text node 
       if (!currentBuffer.Contains("<") && !currentBuffer.Contains(">")) return true; 
       //> and no < will never happen, we will pass it on to the parser when we get to > 
       //by default, don't block 
       return false; 
      }; 
+1

su contador falla en este caso, que es * perfectamente * legal XML: , donde el límite de lectura es anterior a baz. –

Respuesta

2

XmlReader almacena en búferes en trozos de 4kB, si recuerdo cuando lo examiné hace un par de años. Podrías rellenar tus datos entrantes a 4kB (ick!), O usar un analizador mejor. He arreglado esto portando de James Clark XP (Java) para C# como parte de Jabber-Net, aquí:

http://code.google.com/p/jabber-net/source/browse/#svn/trunk/xpnet

Es LGPL, sólo se ocupa de UTF-8, no se empaqueta para su uso, y tiene casi ningún documentación, por lo que no recomendaría su uso. :)

+0

¿Podría darme un resumen rápido sobre cómo usar este analizador? ¿Varias instancias analizarán diferentes sockets de forma asíncrona sin requerir su propio hilo? (? Como en XMPP) –

+1

Ver: http://code.google.com/p/jabber-net/source/browse/trunk/jabber/protocol/AsynchElementStream.cs para un ejemplo. Cree un UTF8Encoding, eche bytes en él con tokenizeContent o tokenizeCdataSection, observe los tokens que salen. Depende de usted la procedencia de los bytes y la sincronización para garantizar que no está modificando el estado de un analizador en diferentes hilos depende de usted. Si quieres hacer XMPP, puedes usar todo Jabber-Net y ahorrarte un poco de molestia. –

+0

Por lo tanto, parecería que la solución * general * es encontrar un analizador xml con una interfaz que me permita poner bytes en mi propio ocio en lugar de suministrar una transmisión. El analizador analizará el contenido tal como lo proporciono, conservando los bytes que aún no ha analizado debido a que no es un nodo xml completo. ¿Suena bien? –

1

Esto es realmente difícil, porque XmlReader no proporciona ninguna interfaz asíncrona.

No estoy muy seguro de cuánto de forma asíncrona se comporta el BeginRead cuando se pide que se lea 0 bytes - que bien podría invocar la devolución de llamada de inmediato y luego bloquear cuando se llama Read. Esto podría ser lo mismo que llamar al Read directamente y luego programar el siguiente Read en un grupo de subprocesos, por ejemplo, usando QueueWorkItem.

Puede ser mejor utilizar BeginRead en la transmisión de red para leer datos, por ejemplo, en fragmentos de 10kB (mientras el sistema espera los datos, no estaría bloqueando ningún hilo). Cuando recibe un fragmento, lo copia en algún local MemoryStream y su XmlReader estaría leyendo datos de este MemoryStream.

Sin embargo, esto todavía tiene un problema: después de copiar 10kB de datos y llamar al Read varias veces, la última llamada se bloquearía. Entonces es probable que necesite copiar trozos de datos más pequeños para desbloquear la llamada pendiente al Read. Una vez hecho esto, podría volver a iniciar una nueva llamada BeginRead para leer una mayor porción de datos de forma asincrónica.

Honestamente, esto suena bastante complicado, así que estoy bastante interesado si alguien viene con una mejor respuesta. Sin embargo, le brinda al menos algunas operaciones asíncronas garantizadas que toman algo de tiempo y no bloquean ningún subproceso mientras tanto (que es el objetivo esencial de la programación asincrónica).

(Nota al margen: Usted podría tratar de usar F# asynchronous workflows a escribir esto, porque hacen un código asíncrono mucho más simple La técnica que he descrito será complicado incluso en F # embargo.)

+0

Hice una prueba rápida y BeginRead'ing 0 bytes está perfectamente bien, la devolución de llamada no se invoca hasta que algunos datos estén listos. Voy a tener una oportunidad para tu algoritmo ahora –

+0

Además, si conociera la longitud del mensaje, el problema que describes no existiría, ¿o sí? –

+0

Si BeginRead lo hace esperar al menos algunos datos, entonces probablemente esté bien (si está descargando trozos pequeños). Si conocía la longitud del mensaje (un elemento), entonces podría leer exactamente la cantidad de bytes necesarios para realizar la siguiente llamada 'Lectura'. Pero esto puede ser aún problemático (por ejemplo, con diferentes codificaciones de texto, etc.) –

2

La cosa más fácil que hacer es simplemente colóquelo en otro hilo, tal vez un ThreadPool dependiendo de cuánto tiempo permanezca activo. (No use subprocesos de grupo de subprocesos para tareas verdaderamente de larga ejecución).

+0

Pensé que un hilo por cliente no se escalaba muy bien? –

+0

No es así. No dije necesariamente un hilo por cliente :) – kyoryu

+0

Entonces, si cada cliente tuviese su propio flujo xml por la vida de la conexión, ¿cómo evitaría tener cada XmlReader en su propio hilo? –

0

¿Está buscando algo así como el método XamlReader.LoadAsync?

Una operación de carga XAML asíncrono inicialmente devolver un objeto que es puramente el objeto raíz. De forma asincrónica, continúa el análisis XAML y luego , y cualquier objeto hijo es completado en la raíz.

+0

No creo que XamlReader active eventos cuando haya nuevos nodos disponibles, solo cuando haya terminado de cargar el marcado, que, en mi caso, sería cuando la conexión se cierre. Sería un uso interesante de xaml aunque: P –

+0

Pensado como mucho. Dejando mi respuesta en caso de que ayude a alguien más después ... –

1

Parece que DOT NET 4.5 tiene una propiedad bool Async en XmlReader, que no está en 3.5. Tal vez eso funcione para usted?

2

XmlReader en .NET 4.5 tiene async versiones de la mayoría de los métodos que implicarían IO.

Compruebe el código de ejemplo here.