Grand Central Dispatch ?

NseaProtectorNseaProtector Membre
17:58 modifié dans API AppKit #1
Bonsoir,
Si j'ai bien compris désormais sous Léo, nous devrions remplacer les threads par ce que propose la nouvelle API "Grand central Dispatch".
Par quoi je remplace mon thread ? Une simple indication pour que je me lance a essayer ça ?  :o
Je vous joint le code que je compte adapter :
@implementation AppController<br /><br />- (id)init<br />{<br />&nbsp; &nbsp; self = [super init];<br />&nbsp; &nbsp; if (self) <br />	{<br />		// Mise en place de valeur par defaut<br />		intervalTempo = 1250; // ecart pour 120BPM et 25 pas à  la noire.<br />		isRunning = TRUE;<br />//Timer qui update l&#39;interface<br />		bpmTimer = [[NSTimer scheduledTimerWithTimeInterval:0.4<br />													 target:self<br />												&nbsp;  selector:@selector(pulseBpm:)<br />												&nbsp;  userInfo:nil<br />													repeats:YES] retain];<br />		NSLog(@&quot;playNextCue: timer started&quot;);<br />//Thread qui incrémente &quot;mon horloge&quot; en arrière plan<br />		[NSThread detachNewThreadSelector:@selector(tempoClock:)toTarget:self withObject:[bpmText stringValue]];<br />		NSLog(@&quot;Thread started&quot;);<br />		return 0 ;<br />&nbsp; &nbsp; }<br />}<br />- (void) pulseBpm:(NSTimer*)aTimer<br />{<br />//	NSLog(@&quot;Horloge: %f&quot;,clockTempo);<br />//horloge est un NSTextField, clockTempo un float<br />	[horloge setFloatValue:clockTempo];<br />		}<br />//-----------------------------------------------------------<br />//				SECTION DES THREADS<br />//-----------------------------------------------------------<br />#pragma mark -<br />#pragma mark Threads <br />-(void)tempoClock:(NSString *)directoryName<br />{<br />	NSAutoreleasePool	*pool = [[NSAutoreleasePool alloc] init];<br />	// boucle while<br />	// tant que la condition n&#39;est pas remplie, on incrémente l&#39;horloge<br />	while (isRunning == TRUE)<br />	{<br />		clockTempo++;<br />		usleep(intervalTempo);<br />	}<br />	[pool release];<br />	<br />}

Réponses

  • zoczoc Membre
    novembre 2009 modifié #2
    dans 1258143106:
    Si j'ai bien compris désormais sous Léo
    Sous Snow Leopard  ;)
    Et ce n'est pas une obligation, les threads continuent à  fonctionner, hein...
    Il y a plein d'articles intéressants sur GCD et les blocks sur NSBlog:


    Mais en gros, ca devrait ressembler à  ça :

    <br /> dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{<br />        [self tempoClock: [bpmText stringValue]];<br />    });<br />
    


    à  la place de la ligne qui démarre le thread.

    Mais attention, à  partir du moment ou GCD (ou même uniquement les blocks) est utilisé, l'application ne peut fonctionner que sous Snow Leopard. Adieu la compatibilité avec Leopard et précédents...
  • AliGatorAliGator Membre, Modérateur
    17:58 modifié #3
    Hello,
    Oui, c'est ce que j'allais dire, ce n'est pas une obligation d'utiliser GCD à  la place des threads, les threads existeront toujours. Après, en effet si tu as l'occasion d'essayer GCD, c'est une bonne chose que d'essayer de t'y mettre... Mais...

    En fait, GCD est basé sur des "tâches" que l'on veut exécuter de manière asynchrone. On lui passe une fonction, ou un "Block" (code encapsulé dans un objet, en quelques sortes), et on lui laisse l'exécuter quand il aura le temps. Avec des fonctions propres à  GCD qui permettent de synchroniser les appels à  bouts de code qu'une fois que l'autre bout de code est fini, etc...

    Là  j'avoue que sur ton code, j'ai du mal à  voir ce qu'il fait, mais aussi l'intérêt d'utiliser un thread ici alors que tu pourrais t'en passer à  priori si j'ai bien compris ton code... et en plus tu n'as rien protégé !

    Alors d'une, si tu gardais les threads, il faudrait protéger tes variables. Là  tu as une variable "clockTempo" qui est accédée à  la fois par ton thread principal dans pulseBpm, et dans ton thread tempoClock, et tu n'as mis aucun mutex pour protéger l'accès concurrent entre les threads.

    D'autre part, pourquoi utiliser un thread ici, dont finalement le rôle revient à  compter le nombre de fois que tu as passé intervalTempo microsecondes, alors qu'il y a des solutions plus simples et moins "dangereuses" ?

    Car en gros si j'ai bien compris tu as un truc que tu peux mettre en lecture et en pause et tu veux savoir où tu en es de ta lecture, c'est ça ?

    Donc déjà , plutôt que ton thread, tu pourrais utiliser simplement un NSTimer qui se déclenche toutes les intervalTempo, non ? en mode repeat:YES ? Ca ne suffirait pas ?

    Et sinon tu peux faire un truc ressemblant à  ça, dans l'idée :
    -(void)setRunning:(BOOL)run<br />{<br />&nbsp; isRunning = run;<br />&nbsp; if (run) // on démarre ou sort de pause<br />&nbsp; {<br />&nbsp; &nbsp; [lastRunDate release];<br />&nbsp; &nbsp; lastRunDate = [[NSDate date] retain];<br />&nbsp; } else { // on met en pause, stocker le nombre de secondes écoulées jusqu&#39;alors<br />&nbsp; &nbsp; cumulTempo += -[lastRunDate timeIntervalSinceNow]; <br />&nbsp; }<br />}<br />-(NSTimeInterval)clockTempo<br />{<br />&nbsp; return cumulTempo + -[lastRunDate timeIntervalSinceNow];<br />}<br />-(void)resetClockTempo<br />{<br />&nbsp; cumulTempo = 0;<br />}<br />-(void)dealloc<br />{<br />&nbsp; [lastRunDate release];<br />&nbsp; // ...<br />&nbsp; [super dealloc];<br />}
    
    Avec ça, quand tu veux démarrer ta lecture depuis zéro, tu fais [self resetClockTempo] pour remettre ton compteur à  zéro, puis un [self setRunning:YES] pour démarrer.
    Ensuite, si tu veux mettre en pause, tu fais [self setRunning:NO] pour mémoriser le cumul de temps écoulé jusque là , et comme ça quand tu veux enlèver ta pause, tu refais un [self setRunning:YES], ça va reprendre comme date de référence la date actuelle (donc tes différences entre lastStartDate et Now seront OK), mais grace à  cumulTempo tu n'auras pas perdu les secondes et millisecondes déjà  accumulées avant ta pause...

    Après, tu peux faire ça sous cette forme, ou avec un NSTimer, mais utiliser un NSThread pour ça, c'est un peu violent :P

    Et en plus, je suis même pas sûr que pour ton cas, ou ton thread boucle, l'utilisation de GCD t'apporte qqch, car il est plus orienté exécutions de tâches (un peu comme NSOperation), moins pour un thread qui tourne en tâche de fond en continu.
  • NseaProtectorNseaProtector Membre
    17:58 modifié #4
    Merci Zoc & Ali pour ces réponses rapides.
    En effet cela ne semble pas si compliqué GCD... (à  vérifier en pratique)
    Pour clarifier les interrogations d'Ali :
    C'est un bout de code d'essais en vue de réaliser un séquenceur DMX. L'idée c'est que le thread ou les threads devront sans être gelé envoyer des boucles de valeurs en tâche de fond et cadencé selon le rythme. (Je ne suis pas sur de l'organisation exact encore ...)
    Quand a l'utilisation de GCD, c'est juste une curiosité et je me demandais si dans mon cas cela ne pouvait pas simplifier ou apporter quelque chose .

  • AliGatorAliGator Membre, Modérateur
    17:58 modifié #5
    Je vois pour ma part plus ça comme une API pour rendre les tâches asynchrones. Ce n'est sans soute pas son seul but, certes. Mais c'est pour ça que je parlais de "tâches" plus haut, une tâche à  exécuter à  un moment donné, mais qui prend du temps donc tu veux pas qu'elle bloque ton appli pour autant. Moins un remplacement pour un thread qui tourne en boucle en tâche de fond (où pour ça les NSThreads et les NSRunLoops si besoin sont là  pour ça)

    Par exemple, imagine que tu as une tableView, et que quand tu cliques sur un bouton, ça va faire tout plein de calculs qui vont remplir un NSArray de valeurs longues à  calculer, et que tu vas ensuite afficher dans ton tableView.
    Si tu fais ça d'une traite :
    NSArray* newData = [self computeData]; // imaginons que ça prend du temps car fait des calculs longs<br />self.tableViewData = newData; // on met à  jour le tableau qui sert de source pour remplir la tableView<br />[tableView reloadData]; // on demande à  la tableView de se recharger pour faire apparaà®tre les nouvelles données
    
    Si tu codes ça comme ça, et si computeData prend beaucoup de temps, ton application va bloquer (risque de roue multicolore au passage) pendant que computeData fera sa tambouille...
    Dans ce genre de cas, GCD te permet de dire "bon, déporte le calcul de computeData dans un thread séparé, tu fais cette tâche en tâche de fond, et quand tu as fini, tu mets à  jour la tableView. Ce qui donne la modification suivante :
    dispatch_async(dispatch_get_global_queue(0, 0), ^{<br />&nbsp; &nbsp; NSArray* newData = [self computeData]; // exécuté dans un thread GCD dédié, et quand terminé...<br />&nbsp; &nbsp; dispatch_async(dispatch_get_main_queue(), ^{ // alors ajouter ce bloc de code dans la main queue (le main thread)<br />&nbsp; &nbsp; &nbsp; &nbsp; self.tableViewData = newData;<br />&nbsp; &nbsp; &nbsp; &nbsp; [tableView reloadData]; // (car les opérations de mise à  jour de la GUI doivent se faire dans le mainThread)<br />&nbsp; &nbsp; });<br />});
    
  • NseaProtectorNseaProtector Membre
    17:58 modifié #6
    Donc si je pige bien, il est préférable que je garde mon bon vieux thread qui "me sert d'horloge de battements" en tâche de fond.
    Parcontre si en dehors du thread je ne fait que lire la variable, tu penses que je dois la protéger quand même ?
  • AliGatorAliGator Membre, Modérateur
    17:58 modifié #7
    Oh que oui tu dois la protéger !
    (Et mêmeque ça serait plus simple d'utiliser un timer, qui utilise la RunLoop de ton mainThread pour se déclencher et non un thread séparé, donc moins de risques si tu ne maà®trises pas les threads d'avoir des pb comme des deadlocks ou des variables non protégées et des crash...)
  • NseaProtectorNseaProtector Membre
    17:58 modifié #8
    dans 1258148563:

    Oh que oui tu dois la protéger !
    (Et mêmeque ça serait plus simple d'utiliser un timer, qui utilise la RunLoop de ton mainThread pour se déclencher et non un thread séparé, donc moins de risques si tu ne maà®trises pas les threads d'avoir des pb comme des deadlocks ou des variables non protégées et des crash...)

    Ok, Merci encore. Les timers dans la RunLoop, ça va geler mon séquenceur des que je vais appuyer sur un bouton ou agrandir ma fenêtre par exemple.
  • GreensourceGreensource Membre
    17:58 modifié #9
    Coucou! Je reprend ce sujet car actuellement je prépare une veille techno sur Grand Central Dispatch.
    Sur cette autre discussion il est dit que les NSOperation utilisait automatiquement GCD si le système sur lequel est lancé l'application dispose de GCD. J'aurais voulu quelques précisions parce que je suis justement entrain de lire la documentation d'Apple sur GCD et la programmation parallèle et pourtant je ne trouve aucun endroit ou il est dit que GCD est utilisé pas les NSOperation.

    Ce que je comprend dans la Doc, c'est que si je veux utiliser GCD je dois faire un block et l'ajouter dans une "dispatch queue".

    Pourtant ça semble en effet logique que NSOperation utilise GCD, d'autant qu'Apple en faisant la promo de GCD disait que juste en re-compilant une application multi-threadé cela permettait d'augmenter la performance d'une appli. Ce qui laisse supposer la modification des API existantes (NSOperation?) pour qu'elles utilisent désormais GCD. J'aimerais juste savoir si quelqu'un sait ou trouver cette information?

    Merci
  • zoczoc Membre
    17:58 modifié #10
    http://developer.apple.com/mac/library/documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html


    An operation object is a single-shot object"that is, it executes its task once and cannot be used to execute it again. You typically execute operations by adding them to an operation queue (an instance of the [color=rgb(51, 102, 204)]NSOperationQueue[/color] class). An operation queue executes its operations either directly, by running them on secondary threads, or indirectly using the libdispatch library (also known as Grand Central Dispatch). For more information about how queues execute operations, see [color=rgb(51, 102, 204)]NSOperationQueue Class Reference[/color].
Connectez-vous ou Inscrivez-vous pour répondre.