2017-06-09 5 views
0

Je travaille avec un périphérique externe dont je reçois des données. Je veux gérer sa file d'attente de lecture/écriture de données de manière asynchrone, dans un thread. Je l'ai surtout fait fonctionner: Il y a une classe qui gère simplement les deux flux, en utilisant le NSStreamDelegate pour répondre aux données entrantes, et en répondant à NSStreamEventHasSpaceAvailable pour envoyer des données qui attendent dans un tampon après avoir échoué à envoyer plus tôt.E/S asynchrones NSStream avec GCD

Cette classe, appelons-la SerialIOStream, ne connaît pas les threads ou les files d'attente GCD. Au lieu de cela, son utilisateur, appelons-le DeviceCommunicator, utilise une file d'attente GCD dans laquelle il initialise la classe SerialIOStream (qui à son tour crée et ouvre les ruisseaux, les programmer dans le runloop en cours):

ioQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); 
dispatch_async(ioQueue, ^{ 
    ioStreams = [[SerialIOStream alloc] initWithPath:[@"/dev/tty.mydevice"]]; 
    [[NSRunLoop currentRunLoop] run]; 
}); 

De cette façon, la SerialIOStream La méthode stream:handleEvent: s'exécute dans cette file d'attente GCD, apparemment.

Toutefois, cela provoque certains problèmes. Je crois que je rencontre des problèmes de concurrence, jusqu'à obtenir des plantages, principalement au moment de l'alimentation des données en attente dans le flux de sortie. Il y a un élément essentiel dans le code où je passe les données de sortie mises en mémoire tampon au courant, puis voir la quantité de données effectivement accepté dans le cours d'eau, puis retirer cette partie de mon tampon:

NSInteger n = self.dataToWrite.length; 
if (n > 0 && stream.hasSpaceAvailable) { 
    NSInteger bytesWritten = [stream write:self.dataToWrite.bytes maxLength:n]; 
    if (bytesWritten > 0) { 
     [self.dataToWrite replaceBytesInRange:NSMakeRange(0, bytesWritten) withBytes:NULL length:0]; 
    } 
} 

Le code ci-dessus peut se dit de deux endroits:

  1. de l'utilisateur (DeviceCommunicator)
  2. de la méthode stream:handleEvent: locale, après avoir été informé qu'il ya un espace dans le flux de sortie.

Ceux qui peuvent être (bien, sûrement) s'exécutent dans un thread séparé, et donc je dois m'assurer qu'ils n'exécutent pas ce code simultanément.

Je pensais résoudre ce problème en utilisant le code suivant dans DeviceCommunicator lors de l'envoi de nouvelles données sur:

dispatch_async (ioQueue, ^{ 
    [ioStreams writeData:data]; 
}); 

(writeData ajoute les données à dataToWrite, voir ci-dessus, et exécute ensuite le code ci-dessus qui envoie Cependant, cela ne fonctionne pas, apparemment parce que ioQueue est une file d'attente concurrente, qui peut décider d'utiliser n'importe quel thread disponible, et par conséquent conduire à une condition de concurrence lorsque writeData est appelée par le DeviceCommunicator pendant que writeData il y a aussi un appel à partir de stream:handleEvent:, sur des filetages séparés. Donc, je suppose que je mélange les attentes des threads (que je connais un peu plus) à mes apparents malentendus avec les files d'attente GCD.

Comment résoudre cela correctement?

Je pourrais ajouter un NSLock, en protégeant la méthode writeData avec elle, et je crois que cela résoudrait le problème à cet endroit. Mais je ne suis pas si sûr que c'est comme ça que GCD est censé être utilisé - j'ai l'impression que ce serait un jeu d'enfant.Dois-je plutôt faire une classe séparée, en utilisant sa propre file d'attente série, pour accéder et modifier le tampon dataToWrite, peut-être? J'essaye toujours de saisir les modèles qui sont impliqués avec ceci. D'une certaine façon, cela ressemble à un modèle classique de producteur/consommateur, mais à deux niveaux, et je ne le fais pas correctement.

+0

Huh - Je me demande si https://stackoverflow.com/a/31317588/43615 est la réponse à ce que je cherche. –

Répondre

1

Longue histoire, bref: Ne traversez pas les cours d'eau! (haha)

NSStream est une abstraction basée sur RunLoop (c'est-à-dire qu'elle a l'intention de faire son travail en coopération sur un NSRunLoop, une approche qui est antérieure à GCD). Si vous utilisez principalement GCD pour prendre en charge la concurrence dans le reste de votre code, alors NSStream n'est pas un choix idéal pour faire des E/S. GCD fournit sa propre API pour gérer les E/S. Voir la section intitulée «Gestion des E/S de répartition» au this page.

Si vous voulez continuer à utiliser NSStream, vous pouvez le faire en programmant vos NSStream s sur la principale runloop de fil ou vous pouvez démarrer un thread d'arrière-plan dédié, le programmer sur un runloop là-bas, puis maréchal vos données aller-retour entre ce thread et vos files d'attente GCD. (... mais ne faites pas cela, mordez la balle et utilisez dispatch_io.)

+0

On me donne les objets NSStream pendant que je fais des communications Bluetooth dans iOS ('EASession' les fournit), donc il n'y a peut-être aucun moyen de les éviter. J'ai actuellement travaillé autour de mon problème en utilisant '@synchronized (self) {...}' autour des parties critiques, et cela semble fonctionner. Pas beaucoup mieux que d'utiliser NSLocks, cependant, car je suppose que je provoque plus de changement de contexte de thread que nécessaire. –

+1

@ThomasTempelmann Si cela fonctionne pour vous, ne me laissez pas vous arrêter, mais je suis raisonnablement confiant que les objets 'NSStream' n'étaient pas destinés à être utilisés de cette façon, et il peut y avoir des problèmes d'affinité de thread qui ne sont pas apparents en tant que consommateur (c'est-à-dire qu'il pourrait utiliser le stockage local de thread dans les coulisses). Être basé sur NSRunLoop est un indicateur assez fort qu'une classe a été conçue pour être utilisée à partir d'un seul thread, alors gardez cela à l'esprit. – ipmcc