Dépréciation de dispatch_get_current_queue() et pattern "completion block on caller queue"

AliGatorAliGator Membre, Modérateur
décembre 2012 modifié dans Objective-C, Swift, C, C++ #1
Hello guys !



Eh oui, ça m'arrive aussi de poser des questions ^^ En l'occurrence une petite interrogation concernant GCD.



Je viens de voir récemment que la fonction dispatch_get_current_queue() était réservée uniquement au debug, et était dépréciée dans iOS6.



Du coup, moi qui l'utilisais de temps en temps pour appliquer le pattern "signal caller queue when done", je ne vois pas trop comment implémenter ce type de pattern sans dispatch_get_current_queue maintenant qu'elle est dépréciée... si vous avez des idées ?





Le principe de ce que j'appelle le pattern "signal caller queue when done" est le suivant : dans une méthode A, lancer une longue opération, sur une dispatch_queue dédiée, puis une fois fini prévenir le code appelant que l'opération est terminée, via l'appel d'un "completion block"... qui doit idéalement être appelé sur la queue qui a initialement appelé ma méthode A. En gros :
-(void)doSomeLongTaskWithCompletion:(dispatch_block_t)completionBlock<br />
{<br />
  dispatch_queue_t callerQueue = dispatch_get_current_queue(); // la dispatch_queue appelante<br />
  dispatch_queue_t parallelQueue = dispatch_queue_create(...); // ma dispatch_queue dédiée pour faire ma longue opération<br />
  ...<br />
<br />
  // garder la callerQueue vivante le temps que la longue tâche s&#39;exécute sur la parallelQueue, au cas où<br />
  dispatch_queue_retain(callerQueue);<br />
  dispatch_async(parallelQueue, ^{<br />
	// ici une opération qui prend du temps, du code assez long à  exécuter<br />
	// mais qui sera donc exécuté sur la parallelQueue sans bloquer le code appelant<br />
<br />
	// Et une fois l&#39;opération longue terminée, on appelle le completionBlock... sur la queue d&#39;origine<br />
	dispatch_sync(callerQueue, completionBlock);<br />
    dispatch_release(callerQueue); // balancer le précédent retain<br />
  });<br />
}
Ainsi quand le code doSomeLongTaskWithCompletion est appelé, par exemple sur la main queue, ça lance la longue tâche et retourne tout de suite la main, et quand la longue tâche est terminée, le completion block est exécuté sur la queue depuis laquelle doSomeLongTaskWithCompletion, donc ici la main queue. Et bien sûr si doSomeLongTaskWithCompletion est appelé depuis une autre dispatch_queue, ça marche aussi.



Sauf que si dispatch_get_current_queue() est déprécié en iOS6, comment on est sensé faire à  la place ? On est obligé de passer la dispatch_queue_t sur laquelle appeler ce block en paramètre en plus du completionBlock ? C'est un peu lourd et dommage, et en plus ça ne fait que reporter le problème un peu plus haut au niveau de l'appelant qui ne sait pas forcément lui-même sur quelle queue il s'exécute...

Réponses

  • si ça peut t'aider https://github.com/robbiehanson/CocoaLumberjack avait se problème de deprecated sur la fonction en question dans une des version de leur framework.



    ils n'utilisent plus cette fonction et je pense qu'ils ont certainement remplacer cette appel par une autre fonction ou un autre bloc.

    tu devrais jeter un coup d'oe“il sur la dernier version ...



    good luck
  • On s'est posé la question pour un projet sur lequel je suis où on fait abondamment usage de GCD et des blocks pour la gestion de l'asynchrone (avec un empilement de thread, running loop et operation histoire de compléter le tableau).



    Après différentes recherche il apparait que la "bonne" solution pour Apple c'est de ne passe retourner sur la caller queue mais de passer par la main queue, c'est le block appelé qui doit ensuite se charger d'éventuellement refaire un dispatch dans une queue secondaire.



    Autant être clair, je trouve cette approche très mauvaise (tout comme le fait qu'on ne peut assigner une queue à  un thread bien préci et persistant dans le temps).
  • AliGatorAliGator Membre, Modérateur
    décembre 2012 modifié #4
    'xyloweb' a écrit:


    si ça peut t'aider https://github.com/r...CocoaLumberjack avait se problème de deprecated sur la fonction en question dans une des version de leur framework.



    ils n'utilisent plus cette fonction et je pense qu'ils ont certainement remplacer cette appel par une autre fonction ou un autre bloc.

    tu devrais jeter un coup d'oe“il sur la dernier version ...
    Ah ben c'est cool ça me rassure, en plus en posant la question ici j'avais un peu peur d'avoir été le seul sur le forum à  m'être posé ce genre de question un peu pointu... et puis ça tombe bien, je comptais l'intégrer ce fmk de CocoaLumberJack (c'est une de mes premières tâches à  faire sur le nouveau projet du boulot à  la rentrée) image/tongue.png' class='bbc_emoticon' alt=':P' />


    'yoann' a écrit:


    Autant être clair, je trouve cette approche très mauvaise
    Tu parles de l'approche de dispatcher sur le main thread au lieu de la caller queue ?

    Oui je suis plutôt d'accord, ça me parait bizarre aussi...



    Autant je comprend qu'Apple dise "attention si vous utilisez dispatch_get_current_queue() ne faites aucune supposition sur les possibilités de la dispatch_queue retournée, si elle est parallèle ou série, etc, etc", autant si on sait ce qu'on fait, en particulier qu'on s'en sert pour ce pattern de callerQueue, ça me parait bizarre d'aller jusqu'à  la déprécier...





    J'ai aussi des cas où je voudrais exécuter du code un peu plus tard, donc j'utilise typiquement dispatch_after, en général sur la même queue que la queue courante... et du coup, tout pareil, on ne peut plus ! Et je vais être obligé de trouver une autre approche, type [NSRunLoop runMode:beforeDate:] ou performSelector:afterDelay: (qui ne marchent, certes, que s'il y a une NSRunLoop de présente dans le thread courant, donc ça amène à  d'autres réflexions sur des alambiqués pour que mon code soit flexible et marche dans tous les cas...) bref ça complique bien les choses alors que GCD permettait justement de les simplifier !



    Du coup j'ai bien peur de beaucoup moins utiliser dispatch_after qu'avant, qui était pourtant bien utile parfois !
  • FKDEVFKDEV Membre
    décembre 2012 modifié #5
    CocoaLumberJack c'est du debug non ? Donc ils ont le droit de continuer à  utiliser l'API. Et ça se comprend, si on veut logger la queue appelante, on va pas s'amuser à  la passer à  chaque Log.



    D'après ce que je comprends en lisant les sujets sur stackoverflow, pour éviter les deadlock et les effets de bords, Apple ne souhaiterait pas que les fmk appelés utilisent cette fonction sans savoir les propriétés de la queue appelante.

    On doit pouvoir imaginer des cas où cela mène au deadlock, même si dans ton exemple ce n'est pas le cas.



    L'idée c'est que c'est toujours le fmk appelé qui décide de la queue qui appelle le completionblock et si tu veux vraiment exécuter du code dans une dispatch_queue donnée pour le completionBlock, alors tu refais un dispatch toi-même à  l'intérieur du completionBlock.

    Si ce n'était pas le cas, il faudrait toujours passer une dispatch_queue target avec un completionBlock parce que le fmk appelé ne pourrait pas toujours supposer qu'il peut utiliser la queue appelante.
  • AliGatorAliGator Membre, Modérateur
    Oui @FKDEV c'est ce que j'ai conclu aussi :

    - Soit on dispatch le completionBlock sur la dispatch_get_main_queue(), et c'est lorsque l'on écrit le completionBlock (dont le code sera donc exécuté sur le main thread et la main queue) qu'on doit redispatcher sur un autre thread si c'est nécessaire

    - Soit on passe la dispatch_queue_t en paramètre en plus du block



    Bref, rien de follichon comme solution...



    Et du côté d'un alternative avec NSOperationQueue ? Je pense à  ça en voyant l'API de "[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]"...

    Du genre
    NSOperationQueue* targetQueue = [NSOperationQueue currentQueue] ?: [NSOperationQueue mainQueue]; // currentQueue, or mainQueue if no OperationQueue present<br />
    [targetQueue addOperationWithBlock:completionBlock];
    
    Mais autant une NSOperationQueue utilise, depuis iOS4, GCD pour exécuter ses opérations, autant ça veut pas dire pour autant que toute dispatch_queue_t a une [NSOperationQueue currentQueue] correspondante... Donc j'ai pas testé mais j'ai bien peur que ça ne marche pas mieux...
  • En fait pour bien comprendre la suppression de dispatch_get_current_queue il faut se rappeler que les queues sont géré automatiquement, les thread associés peuvent disparaitre à  tout moment et d'autres sont crées plus tard pour gérer un appel.



    De fait lorsque on utilise dispatch_get_current_queue c'est tendancieux car on prétend appeler sur la même file mais on ne parle ici que de gestion de priorité et non thread (et donc pas de contexte mémoire).



    Pour éviter toute ambiguité de fonctionnement, il est en effet cohérent que le dispatch_get_current_queue n'existe plus et qu'à  la place le développeur indique précisément la file à  utiliser.



    Toute l'incohérence de dispatch_get_current_queue vient du fait que queue != thread, hors la où appeler un code sur un thread précis à  de l'importance, appeler un code sur une file précise un peu moins...



    Je sais pas si j'arrive bien à  expliquer l'idée, il est tôt pour moi là  :-p
  • AliGatorAliGator Membre, Modérateur
    Si si tu as tout à  fait raison, tu as même mis dans le mile car en étudiant un peu la question et me replongeant dans tout ça l'autre soir je me suis justement surpris à  un moment à  confondre thread et queue avant de réaliser que je faisais parfois l'amalgame...



    Et du coup entre autre pour ce pattern c'est logique qu'il n'y ait pas spécialement de sens à  vouloir "appeler sur la caller queue", contrairement à  la logique qu'on peut avoir de vouloir "appeler sur le caller thread".



    ----





    Sinon pour contourner une autre utilisation qu'on voyait souvent de dispatch_get_current_queue(), CocoaLumberJack a trouvé une parade pas bête. C'est dans le cas de dispatch_sync pour éviter les dead locks : Au lieu de faire
    if (myQueue == dispatch_get_current_queue()) {<br />
      myBlock(); // si on est déjà  sur la dispatch_queue myQueue, éviter le deadlock que ferait un dispatch_sync sur elle-même<br />
    } else {<br />
      dispatch_sync(myQueue, myBlock);<br />
    }
    
    Du coup l'idée c'est d'utiliser dispatch_queue_set_specific pour affecter, après la création de notre dispatch_queue myQueue, une valeur arbitraire (non-nil) à  une clé tout aussi arbitraire. Et pour vérifier ensuite si on est déjà  sur myQueue ou pas, on regarde si dispatch_get_specific() -- qui nous retourne la valeur d'une clé associée à  la dispatch_queue courante -- nous retourne notre valeur ou nous retourne NULL.
    static void *const GlobalLoggingQueueIdentityKey = (void *)&amp;GlobalLoggingQueueIdentityKey;<br />
    <br />
    -(void)initQueues {<br />
    [color=#333333][font=Consolas,]  loggingQueue = dispatch_queue_create(&quot;cocoa.lumberjack&quot;, NULL);[/font][/color]<br />
      void *nonNullValue = GlobalLoggingQueueIdentityKey; // Whatever, just not null<br />
      dispatch_queue_set_specific(loggingQueue, GlobalLoggingQueueIdentityKey, nonNullValue, NULL);<br />
    }<br />
    <br />
    <br />
    - (BOOL)isOnGlobalLoggingQueue<br />
    {<br />
      return (dispatch_get_specific(GlobalLoggingQueueIdentityKey) &#33;= NULL);<br />
    }
    
    C'est bien vu je trouve image/tongue.png' class='bbc_emoticon' alt=':P' />
  • xylowebxyloweb Membre
    décembre 2012 modifié #9
    la piste CocoaLumberjack était donc intéressante image/thumbsup.gif' class='bbc_emoticon' alt='' />

    merci qui? merci xyloweb image/clap.gif' class='bbc_emoticon' alt=' :D ' />
  • AliGatorAliGator Membre, Modérateur
    Tout à  fait !



    La solution de CocoaLumberHack ne m'a pas permis de résoudre ma question du pattern "dispatch on caller queue" (qui de toute façon n'a pas de sens comme mentionné par Yoann) mais m'a permis de trouver quelques bonnes idées pour le remplacement de dispatch_get_current_queue() quand il est utilisé dans d'autres patterns, c'est assez intéressant !
Connectez-vous ou Inscrivez-vous pour répondre.