2010-02-15 5 views
7

C'è un modo per accedere a XmlReader in modo asincrono? L'xml sta uscendo dalla rete da molti client diversi come in XMPP; è un flusso costante di tag <action>...</action>.Asynchronous XmlReader in .NET?

Quello che voglio è poter utilizzare un'interfaccia BeginRead/EndRead-like. La soluzione migliore che ho trovato è quella di eseguire una lettura asincrona per 0 byte sul flusso di rete sottostante, quindi quando arrivano alcuni dati, chiama Read su XmlReader- questo tuttavia bloccherà fino a tutti i dati dal nodo diventa disponibile. Tale soluzione si presenta più o meno come questo

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); 
} 

EDIT: Questo è un possibile algoritmo per lavorare fuori se una stringa 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

il contatore non funziona in questo caso, che è * perfettamente * XML legale: , dove il limite di lettura è precedente a baz. –

risposta

2

buffer XmlReader in blocchi da 4 KB, se ricordo da quando ho guardato a questo un paio di anni fa. Puoi tamponare i tuoi dati in entrata a 4kB (ick!), O usare un parser migliore. Ho fissato questo porting di James Clark XP (Java) per C# come parte di Jabber-Net, qui:

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

E 'LGPL, gestisce solo UTF8, non è confezionato per l'uso, e non ha quasi documentazione, quindi non lo consiglierei di usarlo. :)

+0

Potresti darmi una breve carrellata su come utilizzare questo parser? Più istanze analizzano diversi socket in modo asincrono senza richiedere il proprio thread? (? Come in XMPP) –

+1

Vedi: http://code.google.com/p/jabber-net/source/browse/trunk/jabber/protocol/AsynchElementStream.cs per un esempio. Crea un UTF8Encoding, getta byte con tokenizeContent o tokenizeCdataSection, guarda i token che escono. Da dove provengono i byte, e la sincronizzazione per assicurarti di non modificare lo stato di un parser su thread diversi dipende da te. Se vuoi fare XMPP, puoi semplicemente usare tutto il Jabber-Net e risparmiarti qualche problema. –

+0

Quindi, sembrerebbe che la soluzione * general * sia quella di trovare un parser xml con un'interfaccia che mi permetta di mettere i byte dentro di me a mio piacimento invece di fornire un flusso. Il parser analizzerà il contenuto mentre lo fornisco, mantenendo byte che non ha ancora analizzato a causa del fatto che non è un nodo xml completo. Suona bene? –

1

Questo è veramente difficile, perché XmlReader non fornisce alcuna interfaccia asincrona.

Non sono sicuro di quanto si comporti in modo asincrono lo BeginRead quando si chiede di leggere 0 byte: potrebbe anche richiamare immediatamente la richiamata e quindi bloccare quando si chiama Read. Potrebbe essere la stessa cosa di chiamare direttamente Read e quindi programmare il prossimo Read in un pool di thread, ad esempio utilizzando QueueWorkItem.

Potrebbe essere preferibile utilizzare BeginRead sul flusso di rete per leggere i dati, ad esempio in blocchi da 10 KB (mentre il sistema attende i dati, non si bloccherebbe alcun thread). Quando si riceve un chunk, lo si copierà in alcuni locali MemoryStream e il proprio XmlReader leggerà i dati da questo MemoryStream.

Questo ha comunque un problema: dopo aver copiato 10kB di dati e chiamato Read diverse volte, l'ultima chiamata si bloccherebbe. Quindi probabilmente dovrai copiare blocchi di dati più piccoli per sbloccare la chiamata in attesa a Read. Una volta fatto, è possibile iniziare nuovamente una nuova chiamata BeginRead per leggere una porzione più ampia di dati in modo asincrono.

Onestamente, questo sembra piuttosto complicato, quindi sono abbastanza interessato se qualcuno ha una risposta migliore. Tuttavia, ti dà almeno alcune operazioni asincrone garantite che richiedono del tempo e non bloccano alcun thread nel frattempo (che è l'obiettivo essenziale della programmazione asincrona).

(Nota a margine: Si potrebbe provare a utilizzare F# asynchronous workflows a scrivere questo, perché fanno sì che il codice asincrono molto più semplice La tecnica che ho descritto sarà difficile anche in F # però.)

+0

Ho lanciato un test rapido e BeginReading a 0 byte è perfettamente a posto, il callback non viene richiamato finché alcuni dati non sono pronti. Avrò un colpo al tuo algoritmo ora –

+0

Inoltre, se conoscessi la lunghezza del messaggio, il problema che descrivi non esisterebbe, vero? –

+0

Se BeginRead fa attendere almeno alcuni dati, probabilmente è ok (se stai scaricando piccoli pezzi). Se si conoscesse la lunghezza del messaggio (una lunghezza ), si potrebbe leggere esattamente l'ammontare dei byte necessari per eseguire la successiva chiamata "Leggi". Ma questo può essere ancora problematico (ad es. Con codifiche di testo diverse, ecc.) –

2

La cosa più facile da fare è basta metterlo su un altro filo, forse un ThreadPool a seconda di quanto a lungo rimane attivo. (Non utilizzare thread thread di thread per attività veramente di lunga durata).

+0

Pensavo che un thread-per-client non fosse in scala molto bene? –

+0

Non è così. Non ho necessariamente detto un thread per client :) – kyoryu

+0

Quindi, se ogni client ha il proprio stream xml per tutta la durata della connessione, come si eviterà di avere ogni XmlReader nella propria thread? –

0

Stai cercando qualcosa come il metodo XamlReader.LoadAsync?

Un'operazione carico XAML asincrona inizialmente restituire un oggetto che è puramente l'oggetto principale. In modo asincrono, l'analisi XAML quindi continua e qualsiasi oggetto figlio è inserito nella radice.

+0

Non penso che XamlReader attivi gli eventi quando i nuovi nodi saranno disponibili, solo quando avrà completato il caricamento del markup, che, nel mio caso, avverrà quando la connessione verrà chiusa. Sarebbe un uso interessante di xaml però: P –

+0

Pensato tanto. Lasciare la mia risposta anche se nel caso in cui aiuti qualcun altro più tardi ... –

1

Sembra che DOT NET 4.5 abbia una proprietà bool Async su XmlReader, che non è presente in 3.5. Forse funzionerà per te?

2

XmlReader in .NET 4.5 ha async versioni della maggior parte dei metodi che coinvolgono IO.

Controllare il codice di esempio here.