Lire continuellement dans un stream?

J’ai un object Stream qui reçoit parfois des données, mais à des intervalles imprévisibles. Les messages qui apparaissent sur le stream sont bien définis et déclarent la taille de leur charge utile à l’avance (la taille est un entier de 16 bits contenu dans les deux premiers octets de chaque message).

J’aimerais avoir une classe StreamWatcher qui détecte quand le Stream contient des données. Une fois que ce sera fait, je souhaiterais qu’un événement soit déclenché afin qu’une instance StreamProcessor souscrite puisse traiter le nouveau message.

Cela peut-il être fait avec des événements C # sans utiliser directement les threads? Il semble que cela devrait être simple, mais je ne parviens pas à comprendre le bon moyen de concevoir cela.

Quand vous dites de ne pas utiliser les threads directement , je suppose que vous voulez toujours les utiliser indirectement via des appels asynchrones, sinon cela ne serait pas très utile.

Tout ce que vous avez à faire est d’envelopper les méthodes asynchrones du Stream et de stocker le résultat dans une mémoire tampon. Tout d’abord, définissons la partie événement de la spécification:

 public delegate void MessageAvailableEventHandler(object sender, MessageAvailableEventArgs e); public class MessageAvailableEventArgs : EventArgs { public MessageAvailableEventArgs(int messageSize) : base() { this.MessageSize = messageSize; } public int MessageSize { get; private set; } } 

Maintenant, lisez un entier 16 bits du stream de manière asynchrone et signalez quand il est prêt:

 public class StreamWatcher { private readonly Stream stream; private byte[] sizeBuffer = new byte[2]; public StreamWatcher(Stream stream) { if (stream == null) throw new ArgumentNullException("stream"); this.stream = stream; WatchNext(); } protected void OnMessageAvailable(MessageAvailableEventArgs e) { var handler = MessageAvailable; if (handler != null) handler(this, e); } protected void WatchNext() { stream.BeginRead(sizeBuffer, 0, 2, new AsyncCallback(ReadCallback), null); } private void ReadCallback(IAsyncResult ar) { int bytesRead = stream.EndRead(ar); if (bytesRead != 2) throw new InvalidOperationException("Invalid message header."); int messageSize = sizeBuffer[1] << 8 + sizeBuffer[0]; OnMessageAvailable(new MessageAvailableEventArgs(messageSize)); WatchNext(); } public event MessageAvailableEventHandler MessageAvailable; } 

Je crois que c'est à propos de ça. Cela suppose que la classe qui gère le message a également access au Stream et est prête à le lire, de manière synchrone ou asynchrone, en fonction de la taille du message dans l'événement. Si vous voulez que la classe watcher lise le message en entier, vous devrez append du code supplémentaire pour le faire.

L’approche normale consiste à utiliser le modèle de programmation asynchrone .NET exposé par Stream . Essentiellement, vous commencez à lire de manière asynchrone en appelant Stream.BeginRead , en lui transmettant un tampon byte[] et une méthode de rappel qui sera appelée lorsque les données ont été lues dans le stream. Dans la méthode de rappel, vous appelez Stream.EndRead , en lui transmettant l’argument IAsncResult qui a été atsortingbué à votre rappel. La valeur de retour de EndRead vous indique le nombre d’octets lus dans le tampon.

Une fois que vous avez reçu les premiers octets de cette manière, vous pouvez attendre le rest du message (si vous n’avez pas reçu suffisamment de données la première fois) en appelant à nouveau BeginRead . Une fois que vous avez tout le message, vous pouvez relancer l’événement.

L’utilisation de Stream.BeginRead () n’est pas identique à l’utilisation de la méthode synchrone Stream.Read () dans un thread distinct?

Oui, cela peut être fait. Utilisez la méthode Stream.BeginRead non bloquante avec un AsyncCallback . Le rappel est appelé de manière asynchrone lorsque les données deviennent disponibles. Dans le rappel, appelez Stream.EndRead pour obtenir les données, puis appelez à nouveau Stream.BeginRead pour obtenir le bloc de données suivant. Mettez en mémoire tampon les données entrantes dans un tableau d’octets suffisamment volumineux pour contenir le message. Une fois que le tableau d’octets est plein (plusieurs appels de rappel peuvent être nécessaires), déclenchez l’événement. Ensuite, lisez la taille du message suivant, créez un nouveau tampon, recommencez, c’est terminé.