60 votes

Java InputStream lecture bloquante

Selon la documentation API Java, la méthode [read()](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/InputStream.html#read()) déclarée dans InputStream est décrite comme suit :

Si aucun octet n'est disponible car la fin du flux a été atteinte, la valeur -1 est renvoyée. Cette méthode bloque jusqu'à ce que des données d'entrée soient disponibles, que la fin du flux soit détectée ou qu'une exception soit levée.

J'ai une boucle while(true) exécutant un read() et j'obtiens toujours -1 lorsque rien n'est envoyé sur le flux. C'est attendu.

Ma question est la suivante : quand read() bloquerait-il ? Puisque s'il ne reçoit aucune donnée, il renvoie -1. Je m'attendrais à ce qu'un read() bloquant attende l'arrivée de données. Si vous avez atteint la fin du flux d'entrée, read() ne devrait-il pas simplement attendre les données au lieu de renvoyer -1 ?

Ou est-ce que read() bloque uniquement s'il y a un autre thread accédant au flux et que votre read() ne peut pas y accéder ?


Cela m'amène à ma question suivante. J'avais autrefois un gestionnaire d'événements (fourni par ma bibliothèque) qui me notifierait quand des données sont disponibles. Lorsque j'étais notifié, je commençais un cycle while((aByte = read()) > -1) pour stocker l'octet. J'étais perplexe lorsque j'obtenais DEUX événements en très peu de temps et que toutes mes données n'étaient pas affichées. Il semblait que seule la fin de la seconde événement serait affichée et le reste manquait.

J'ai finalement modifié mon code pour que lorsque je reçois un événement, je commence if(inputStream.available() > 0) while((aByte = read()) > -1) pour stocker l'octet. Maintenant, cela fonctionnait correctement et toutes mes données étaient affichées.

Quelqu'un pourrait-il expliquer ce comportement ? La méthode [available()](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/InputStream.html#available()) d'InputStream est censée renvoyer le nombre d'octets que vous pouvez lire avant de bloquer le prochain appelant (du flux ?). Même si je n'utilise pas available(), je m'attendrais à ce que la lecture du premier événement bloque simplement la lecture du second événement, mais n'efface pas ou ne consomme pas trop de données du flux. Pourquoi cela causerait-il l'affichage incomplet de mes données ?

51voto

erickson Points 127945

La source de données sous-jacente pour certaines implémentations de InputStream peut indiquer que la fin du flux a été atteinte et qu'aucune autre donnée ne sera envoyée. Jusqu'à ce que ce signal soit reçu, les opérations de lecture sur un tel flux peuvent être bloquées.

Par exemple, un InputStream provenant d'une prise Socket bloquera, au lieu de renvoyer EOF, jusqu'à ce qu'un paquet TCP avec le drapeau FIN soit reçu. Lorsque EOF est reçu depuis un tel flux, vous pouvez être assuré que toutes les données envoyées sur cette prise ont été reçues de manière fiable et vous ne pourrez plus lire de données. (Si une lecture bloquante entraîne une exception, d'autre part, certaines données peuvent avoir été perdues.)

D'autres flux, comme ceux d'un fichier brut ou d'un port série, peuvent ne pas avoir un format ou un protocole similaire pour indiquer qu'aucune autre donnée ne sera disponible. Ces flux peuvent renvoyer immédiatement EOF (-1) au lieu de bloquer lorsque aucune donnée n'est actuellement disponible. En l'absence d'un tel format ou protocole, cependant, vous ne pouvez pas être sûr que l'autre côté a fini d'envoyer des données.


En ce qui concerne votre deuxième question, il semble que vous ayez rencontré une condition de concurrence. Sans voir le code en question, je suppose que le problème se trouvait en fait dans votre méthode de "display". Peut-être que la tentative d'affichage par la deuxième notification écrasait d'une manière ou d'une autre le travail effectué lors de la première notification.

19voto

Vladimir Dyuzhev Points 10647

Il retourne -1 s'il est arrivé à la fin du flux. Si le flux est encore ouvert (c'est-à-dire, la connexion par socket) mais aucune donnée n'a atteint le côté de lecture (le serveur est lent, le réseau est lent,...), la méthode read() se bloque.

Vous n'avez pas besoin d'appeler available(). J'ai du mal à comprendre la conception de votre notification, mais vous n'avez besoin d'aucun autre appel que read() lui-même. La méthode available() est là juste pour la commodité.

16voto

Guss Points 6512

OK, c'est un peu du désordre, donc première chose commençons par clarifier : InputStream.read() bloquant n'a rien à voir avec le multithreading. Si vous avez plusieurs threads lisant à partir du même flux d'entrée et que vous déclenchez deux événements très proches l'un de l'autre - où chaque thread essaie de consommer un événement alors vous obtiendrez une corruption : le premier thread à lire obtiendra quelques octets (éventuellement tous les octets) et lorsque le deuxième thread sera planifié, il lira le reste des octets. Si vous prévoyez d'utiliser un seul flux IO dans plus d'un thread, toujours synchronized() {} sur une contrainte externe.

Deuxièmement, si vous pouvez lire de votre InputStream jusqu'à ce que vous obteniez -1 puis attendre et pouvez lire à nouveau plus tard, alors l'implémentation de InputStream que vous utilisez est défectueuse ! Le contrat pour InputStream indique clairement qu'un InputStream.read() devrait retourner seulement -1 lorsqu'il n'y a plus de données à lire parce que la fin du flux a été atteinte et qu'il n'y aura PLUS jamais de données disponibles - comme lorsque vous lisez à partir d'un fichier et que vous atteignez la fin(1).

Le comportement "il n'y a plus de données disponibles maintenant, veuillez patienter et vous en obtiendrez davantage" est pour que read() bloque et ne retourne pas tant qu'il n'y a pas de données disponibles (ou qu'une exception est levée).

  1. Comme noté en profondeur dans la discussion sur la réponse de erickson (actuellement en haut), une implémentation de FileInputStream peut en fait lire au-delà de la "fin de fichier" et fournir plus de données après qu'un read() ait retourné -1 - si des données sont ajoutées au fichier plus tard. Ceci est un cas particulier et c'est essentiellement le seul cas de ce genre dans les implémentations courantes de InputStream (ou au pire - très très rare). Vous devriez prendre cela en compte si vous savez que vous utilisez FileInputStream et que vous vous attendez à ce que le fichier que vous lisez ait des données supplémentaires ajoutées (un exemple courant est le suivi d'un fichier journal), mais sinon c'est juste une lacune de l'API InputStream et dans tous les cas - vous seriez mieux loti si vous pouviez arrêter d'utiliser les API d'IO bloquantes de style java.io et utilisez les API d'IO non bloquantes java.nio.

7voto

Robert Points 10865

Par défaut, le comportement du InputStream RXTX fourni n'est pas conforme.

Vous devez définir le seuil de réception à 1 et désactiver le délai de réception :

serialPort.enableReceiveThreshold(1);
serialPort.disableReceiveTimeout();

Source : Connexion série RXTX - problème avec la lecture bloquante()

4voto

Aye! Ne abandonne pas encore ton flux Jbu. Nous parlons ici de communication série. Pour les choses sérieuses, il est absolument attendu qu'un -1 puisse / sera renvoyé à la lecture, et pourtant attendez-vous quand même à des données ultérieurement. Le problème est que la plupart des gens sont habitués à traiter avec TCP/IP qui devrait toujours renvoyer un 0 sauf si le TCP/IP est déconnecté... alors oui, -1 a du sens. Cependant, avec Serial, il n'y a pas de flux de données pendant de longues périodes, pas de "HTTP Keep Alive", ni de battement TCP/IP, ou (dans la plupart des cas) pas de contrôle de flux matériel. Mais le lien est physique, et toujours connecté par "du cuivre" et encore parfaitement actif.

Maintenant, si ce qu'ils disent est correct, c'est-à-dire que Serial devrait être fermé sur un -1, alors pourquoi devons-nous surveiller des choses comme OnCTS, pmCarroerDetect, onDSR, onRingIndicator, etc... Enfin, si 0 signifie que c'est là, et -1 signifie que ce n'est pas le cas, alors laissez tomber toutes ces fonctions de détection ! :-)

Le problème auquel vous pourriez être confronté pourrait résider ailleurs.

Maintenant, passons aux détails :

Q : "Il semblait que seule la fin des données du deuxième événement serait affichée et le reste manquait."

R : Je vais supposer que vous étiez dans une boucle, réutilisant le même tampon de bytes[]. Le premier message arrive, n'est pas encore affiché à l'écran/dans le journal/sortie standard (parce que vous êtes dans la boucle), puis vous lisez le deuxième message, remplaçant les données du premier message dans le tampon. Encore une fois, je vais supposer que vous ne stockez pas combien vous avez lu, et que vous vous assurez de décaler votre tampon de stockage de la quantité lue précédemment.

Q : "J'ai finalement modifié mon code de sorte que quand je reçois un événement j'appelle si(inputStream.available() > 0) while((aByte = read()) > -1) enregistrer le byte."

R : Bravo... c'est ce qu'il faut. Maintenant, votre tampon de données est à l'intérieur d'une instruction IF, votre deuxième message ne mettra pas en péril le premier... en fait, c'était probablement juste un gros message en premier lieu. Mais maintenant, vous le lirez tout d'un coup, gardant les données intactes.

C : "... condition de course..."

R : Ahhh, le bon vieux bouc émissaire de tous les maux ! La condition de course... :-) Oui, cela aurait pu être une condition de course, en fait cela aurait bien pu l'être. Mais cela pourrait aussi être simplement la façon dont le RXTX efface le drapeau. Le fait que le drapeau 'données disponibles' ne se déclare pas aussi rapidement que prévu. Par exemple, quelqu'un connaît-il la différence entre read VS readLine concernant le fait de vider le tampon où les données étaient précédemment stockées et de réinitialiser le drapeau de l'événement ? Moi non plus. :-) Et je n'ai pas encore trouvé la réponse... mais... laissez-moi divaguer pendant quelques phrases de plus. La programmation basée sur les événements a encore quelques défauts. Laissez-moi vous donner un exemple réel avec lequel j'ai récemment dû composer.

  • J'ai reçu des données TCP/IP, disons, 20 bytes.
  • Je reçois l'événement pour les Données Reçues.
  • Je commence même mon 'lecture' sur les 20 bytes.
  • Avant que je n'aie fini de lire mes 20 bytes... j'en reçois 10 autres.
  • Cependant, le TCP/IP regarde pour me notifier, oh, voit que le drapeau est toujours ACTIF, et ne me notifiera pas à nouveau.
  • Cependant, j'ai fini de lire mes 20 bytes (available() disait qu'il y en avait 20)...
  • ... et les 10 derniers bytes restent dans la file d'attente du TCP/IP... parce que je n'ai pas été notifié de leur présence.

Vous voyez, la notification a été ratée parce que le drapeau était toujours actif... même si j'avais commencé à lire les bytes. Si j'avais fini la lecture des bytes, alors le drapeau aurait été effacé, et j'aurais reçu la notification pour les 10 bytes suivants.

L'exact opposé de ce qui se passe pour vous en ce moment.

Alors oui, allez-y avec un IF disponible()... faites une lecture de la longueur des données renvoyées. Ensuite, si vous êtes paranoïaque, réglez une minuterie et appelez de nouveau available(), s'il y a toujours des données, alors faites une lecture des nouvelles données. Si available() renvoie 0 (ou -1), alors détendez-vous... asseyez-vous et attendez la prochaine notification OnEvent.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X