web-dev-qa-db-fra.com

Java Blocage InputStream lu

Selon l'API Java, la InputStream.read() est décrite comme:

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 les 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) faisant une lecture et j'obtiens toujours -1 quand rien n'est envoyé sur le flux. C'est prévu.

Ma question est quand est-ce que read () bloquerait jamais? Puisque s'il n'obtient aucune donnée, il renvoie -1. Je m'attendrais à ce qu'une lecture bloquante attende la réception des données. Si vous avez atteint la fin du flux d'entrée, ne devrait pas lire () simplement attendre les données au lieu de retourner -1?

Ou read () ne bloque-t-il que si un autre thread accède au flux et que read () ne peut pas accéder au flux?


Ce qui m'amène à ma prochaine question. J'avais l'habitude d'avoir un écouteur d'événements (fourni par ma bibliothèque) qui m'avertissait lorsque des données étaient disponibles. Lorsque j'ai été averti, j'appelais while((aByte = read()) > -1) stockait l'octet. J'étais perplexe quand j'obtenais DEUX événements à proximité très proche et toutes mes données n'étaient pas affichées. Il semblait que seule la fin des données du deuxième événement serait affichée et que le reste manquait.

J'ai finalement changé mon code pour que lorsque j'obtiens un événement, j'appelle if(inputStream.available() > 0) while((aByte = read()) > -1) stocke l'octet. Maintenant, cela fonctionnait correctement et toutes mes données étaient affichées.

Quelqu'un peut-il expliquer ce comportement? On dit que la InputStream.available() renvoie 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'attends à ce que la lecture du premier événement bloque simplement la lecture du deuxième événement, mais n'efface pas ou ne consomme pas trop de données de flux. Pourquoi cela ne ferait-il pas apparaître toutes mes données?

51
jbu

La source de données sous-jacente pour certaines implémentations de InputStream peut signaler que la fin du flux a été atteinte et 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 se bloquer.

Par exemple, un InputStream d'un socket Socket se bloquera, plutôt que de retourner EOF, jusqu'à ce qu'un paquet TCP avec l'indicateur FIN soit reçu. Quand = = EOF est reçu d'un tel flux, vous pouvez être assuré que toutes les données envoyées sur ce socket 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, en revanche, 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 donnée supplémentaire ne sera disponible. Ces flux peuvent renvoyer immédiatement EOF (-1) plutôt que de les bloquer lorsqu'aucune donnée n'est actuellement disponible. En l'absence d'un tel format ou protocole, cependant, vous ne pouvez pas être sûr quand l'autre côté est fait l'envoi de données.


En ce qui concerne votre deuxième question, il semble que vous ayez peut-être eu une condition de concurrence. Sans voir le code en question, je suppose que le problème réside en fait dans votre méthode "d'affichage". Peut-être que la tentative d'affichage par la deuxième notification était en quelque sorte en train d'altérer le travail effectué lors de la première notification.

45
erickson

Il renvoie -1 si c'est la fin du flux. Si le flux est toujours ouvert (c'est-à-dire la connexion socket) mais qu'aucune donnée n'a atteint le côté lecture (le serveur est lent, les réseaux sont lents, ...) les blocs read ().

Vous n'avez pas besoin d'appels disponibles (). J'ai du mal à comprendre votre conception de notification, mais vous n'avez pas besoin d'appels, sauf read () lui-même. La méthode disponible () n'est là que pour plus de commodité.

17
Vladimir Dyuzhev

OK, c'est un peu gâchis donc la première chose permet de clarifier les choses: InputStream.read() le blocage n'a rien à voir avec le multi-threading. 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, 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 à partir de votre InputStream jusqu'à ce que vous obteniez -1, puis attendre et pouvoir relire plus tard, l'implémentation InputStream que vous utilisez est interrompue! Le contrat pour InputStream indique clairement qu'une InputStream.read() ne doit renvoyer -1 que lorsqu'il n'y a plus de données à lire car la fin du flux entier a été atteinte et plus aucune donnée ne sera JAMAIS disponible - comme lorsque vous lisez un fichier et que vous atteignez la fin.

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

13
Guss

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

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

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

Source: connexion série RXTX - problème avec le blocage de lecture ()

6
Robert

Toujours! N'abandonnez pas encore votre flux Jbu. Nous parlons ici de communication série. Pour les trucs en série, on s'attend absolument à ce qu'un -1 puisse/soit retourné lors des lectures, mais attend encore des données plus tard. Le problème est que la plupart des gens sont habitués à traiter avec TCP/IP qui devrait toujours retourner un 0 à moins que TCP/IP ne soit déconnecté ... alors oui, -1 est logique. Cependant, avec Serial, il n'y a pas de flux de données pendant de longues périodes, et pas de "HTTP Keep Alive", ou de pulsation TCP/IP, ou (dans la plupart des cas) aucun contrôle de flux matériel. Mais le lien est physique, et toujours connecté par "cuivre" et toujours parfaitement vivant.

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

Le problème auquel vous pouvez être confronté peut se trouver ailleurs.

Maintenant, sur les détails:

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

R: Je vais deviner que vous étiez dans une boucle, en réutilisant le même tampon d'octets []. Le 1er message arrive, n'est pas encore affiché sur l'écran/journal/sortie standard (parce que vous êtes dans la boucle), puis vous lisez le 2ème message, en remplaçant les données du 1er message dans le tampon. Encore une fois, car je vais deviner que vous ne stockez pas ce que vous lisez, puis vous êtes assuré de compenser le tampon de votre magasin par le montant de lecture précédent.

Q: "J'ai finalement changé mon code afin que lorsque j'obtiens un événement, j'appelle if (inputStream.available ()> 0) tandis que ((aByte = read ())> -1) stocke l'octet."

R: Bravo ... c'est la bonne chose là-bas. Maintenant, votre tampon de données est à l'intérieur d'une instruction IF, votre 2e message n'encombrera pas votre 1er ... eh bien, en fait, c'était probablement juste un gros (er) message à la 1ère place. Mais maintenant, vous allez tout lire en une seule fois, en gardant les données intactes.

C: "... condition de concurrence ..."

A: Ahhh, le bon vieux chèvre scape! La condition de course ... :-) Oui, cela pourrait être une condition de course, en fait, elle pourrait bien l'avoir été. Mais, cela pourrait également être la façon dont le RXTX efface le drapeau. La suppression du "drapeau de données disponibles" peut ne pas se produire aussi rapidement que prévu. Par exemple, quelqu'un connaît la différence entre lire VS readLine par rapport à l'effacement du tampon dans lequel les données ont été précédemment stockées et à la réinitialisation de l'indicateur d'événement? Moi non plus :-) Je ne peux pas non plus trouver la réponse pour l'instant ... mais ... permettez-moi de continuer quelques phrases. La programmation événementielle a encore quelques défauts. Permettez-moi de vous donner un exemple concret auquel j'ai dû faire face récemment.

  • J'ai obtenu des données TCP/IP, disons, 20 octets.
  • Je reçois donc l'OnEvent for Received Data.
  • Je commence ma "lecture" même sur les 20 octets.
  • Avant de terminer la lecture de mes 20 octets ... j'obtiens 10 autres octets.
  • Le TCP/IP cherche cependant à me prévenir, oh, voit que le drapeau est toujours défini, et ne me le notifiera plus.
  • Cependant, j'ai fini de lire mes 20 octets (disponible () a dit qu'il y en avait 20) ...
  • ... et les 10 derniers octets restent dans le TCP/IP Q ... car je n'en ai pas été informé.

Vous voyez, la notification a été manquée car l'indicateur était toujours défini ... même si j'avais commencé à lire les octets. Si j'avais fini les octets, alors le drapeau aurait été effacé et j'aurais reçu une notification pour les 10 octets suivants.

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

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

4
Adrian

InputStream est juste une classe abstraite, malheureusement, l'implémentation décide de ce qui se passe.

Que se passe-t-il si rien n'est trouvé:

  • Sockets (c'est-à-dire SocketInputStream) se bloquera jusqu'à la réception des données (par défaut). Mais il est possible de définir un timeout (voir: setSoTimeout), puis le read se bloquera pendant x ms. Si rien n'est encore reçu, un SocketTimeoutException sera lancé.

    Mais avec ou sans timeout, la lecture à partir d'un SocketInputStreampeut parfois entraîner un -1. ( Par exemple, lorsque plusieurs clients se connectent simultanément au même Host:port, même si les périphériques semblent connectés, le résultat d'un read pourrait immédiatement se traduire par un -1 (ne renvoyant jamais de données). )

  • Serialio la communication renverra toujours -1; Vous pouvez également définir un délai d'expiration (utilisez setTimeoutRx), le read bloquera d'abord pendant x ms, mais le résultat sera toujours -1 si rien n'est trouvé. (Remarque: mais plusieurs classes io série sont disponibles, le comportement peut dépendre du fournisseur.)

  • Fichiers (lecteurs ou flux) entraînera un EOFException.

Travail vers une solution générique:

  • Si vous encapsulez l'un des flux ci-dessus dans un DataInputStream, vous pouvez utiliser des méthodes comme readByte, readChar, etc. Tout -1 les valeurs sont converties en EOFException. (PS: si vous effectuez beaucoup de petites lectures, alors c'est une bonne idée de l'envelopper dans un BufferedInputStream d'abord)
  • SocketTimeoutException et EOFException étendent IOException, et il existe plusieurs autres IOException possibles. Il est pratique de simplement vérifier les IOException pour détecter les problèmes de communication.

Un autre sujet sensible est le rinçage. flush en termes de sockets signifie "envoyer maintenant", mais en termes de Serialio cela signifie "jeter le tampon".

2
bvdb