Blocage dispatch_group
Hello,
J'ai un crash avec aucune trace de debug avec l'utilisation de dispatch_group et peut être que quelqu'un aurait une idée
Le code :
@interface PFMAccountSelectionViewController
@property (nonatomic, strong) NSMutableArray *arrayOfAccountsManagerParser;
...
@end
@implementation PFMAccountSelectionViewController
...
@end
- (void)applyChange{
dispatch_group_t group = dispatch_group_create();
// to handle the WS completion
__block BOOL errorDetected = NO;
for (NSInteger i = 0; i < self.arrayOfAccounts.count; i ++)
{
CSPFMAccount * newAccount = self.arrayOfAccounts[i];
CSPFMAccount * oldAccount = [ApplicationData sharedInstance].arrayOfPFMPRCAccounts[i];
if (newAccount.selected != oldAccount.selected)
{
[self lanceSpinnerMiniActivityViewer];
dispatch_group_enter(group);
NSLog(@BEFORE BLOCK : %ld, (long)i);
weakifySelf(weakSelf);
CSPFMAccountsManagerXMLParser * parserAccountsManager = [[CSPFMAccountsManagerXMLParser alloc] initWithCompletion:^(EIXMLDOMParser *parser, NSError *error) {
NSLog(@IN GROUP : %ld, (long)i);
strongifySelfAndReturnIfNil(strongSelf, weakSelf);
if (error != nil)
{
errorDetected = YES;
[EIAlertView presentAlertViewWithError:error];
// We revert the selection
newAccount.selected = !newAccount.selected;
[strongSelf->currentTableView reloadRowsAtIndexPaths:@[;[NSIndexPath indexPathForRow:i inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
}
dispatch_group_leave(group);
}];
[self.arrayOfAccountsManagerParser addObject:parserAccountsManager];
[parserAccountsManager
start:newAccount.webid
action:CSPFMAccountsManagerActionTypeUpdate
isSelected:newAccount.selected
];
}
}
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@OH I'M HERE);
}
En gros :
Si je lance un seul appel : ca passe tout le temps (le NSLog "OH I'M HERE").
Si j'en lance 2, ca passe rarement.
Si j'en lance 3 ou plus je vois dans les traces de log que le web service est bien appelé mais on arrive jamais au "OH I'M HERE".
J'imagine que je ne dois pas retainer correctement un objet, mais j'ai du mal à voir.
Je pourrais faire autrement (avec un compteur ou un truc du genre, mais je trouve le GCD plus propre pour les appels concurrents.
Merci d'avance
Mots clés:
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
A noter que j'avais au début à la place du dispatch_group_wait :
En particulier si tu mets aussi un log juste avant le dispatch_group_enter et juste après le dispatch_group_leave, est-ce que tu as bien les 2 logs, qui montreraient que tu balances les entrées et sorties du groupe ?
Sinon, un petit coup de Static Analyzer (Product -> Analyze) pourrais te donner des indices.
Sinon pour ta solution précédente (2ème message), comme dispatch_group_notify retourne tout de suite (et continue avec le reste du code), pour n'exécuter le block de notification que plus tard (comme pour un completionBlock, quoi), ça veut dire que dans ce cas il faut que ton "dispatch_group_t group" soit retenu quelque part dans une @property(strong). Je pense que c'est pour ça que tu avais un EXC_BADACCESS, ton group ne devait pas être retenu ?
Alors j'ai allégé le code :
En gros on est bien avant et après le Enter, mais on rentre jamais dans les completions block !
Je vais regarder le static analyser.
Tu es sûr que ton completionBlock est bien appelé sur un thread secondaire ?
Parce que si ton completionBlock de ton CSPFMAccountsManagerXMLParser est appelé sur la dispatch_get_main_queue() c'est un peu logique que tu aies un deadlock : ton main thread est bloqué par ton dispatch_group_wait (qui attend que tous les groupes soient finis), et si ton completionBlock essaye de s'exécuter sur ce main thread, comme il est bloqué par le dispatch_group_wait, tu as un deadlock.
En gros le main thread attend que le dispatch_group_wait ait redonné la main, ce qui n'arrivera que quand tes completionBlock seront appelés, mais tes completionBlock s'ils sont exécutés sur le main thread vont à leur tour attendre que le main thread soit libre pour pouvoir s'exécuter... c'est le serpent qui se mord la queue !
Si tu veux que ça marche il ne faut pas que ton completionBlock s'exécute sur le même thread que le thread que tu as bloqué avec ton dispatch_group_wait, sinon ça n'a pas de sens.
Ok, merci pour tout Ali, du coup la j'ai enlevé le _wait et remis un _notify avec le group qui est strong et c'est bon. Je vais quand meme demander à ceux qui ont fait la librairie d'appel des WS (avec les completions block) dans quels threads sont exécutés les completionBlock.
Fausse joie, ca a fonctionné une fois that's all.
Bon j'ai toujours un crash -_- mais après le notify. Je vais creuser.
Je pense que le code est compliqué pour rien ( ou bien j'ai mal compris ?) et c'est un peu normal d'avoir des comportments qui te font crasher.
Pourquoi stocker tous les objets parser dans une liste ? La première chose à mon avis est d'alléger ce code :
Ce n'est pas vraiment une solution à ton problème mais je pense que tu verras plus claire comme ça et et ça t'évitera probablement les crash.
Je les stockes dans un array (strong) sinon ils risquent de ne plus exister et le completion block pourrait ne pas s'executer.
Je vais essayer quand meme de l'appeler directement comme tu le proposes.
Bon c'est pas trop possible. Je dépend d'une lib interne ou il y a :
Et le parser hérite de cette classe.
Et aucun signe de dispatch_queue pour le completionBlock (ni en variable d'instance, ni avec un moyen d'en fournir une lors de l'appel)...
C'est mauvais signe cette lib... Ca sent le completionBlock exécuté sur le mainThread avec aucun moyen de demander de l'exécuter sur une autre queue que la main_queue... du coup tu vas pas pouvoir à la fois faire un dispatch_group_wait dans le mainThread d'un côté et faire tes dispatch_group_leave dans les completion si ces dernières sont dans le main thread...
Soit tu changes de lib, soit tu la fais évoluer pour qu'elle accepte en paramètre la dispatch_queue sur laquelle exécuter le completionBlock, soit tu passes par un autre moyen... comme dispatch_group_notify si jamais il marche pour ce cas.
Oui après discussion avec le mec qui a fait la lib ici, tu as tout juste Ali !
Alors j'ai des infos supplémentaire :
On "laisse" iOS gérer les threads, les completions block sont exécuté dans le meme thread que la NSURLConnexion créée pour l'appel du WS.
Vite fait en pseudo-code (non testé) ça devrait donner un truc du genre :
J'ai testé ca :
Et dans le log :
Et je n'ai pas de trace de l'appel du WS dans les logs.
Et avec
A la place du wait, j'ai bien les After Start et les traces de l'appel du WS, par contre je rentre jamais dans le completion block.
A la limite il suffit dans leur code, juste avant la ligne qui est sensée appeler ton completionBlock, d'afficher la queue voire le NSThread sur lequel il est appelé pour voir s'il s'agit du mainThread ou pas.
Ce qui est bizarre c'est qu'ils te disent "les completions block sont exécuté dans le meme thread que la NSURLConnexion", sauf que justement s'ils utilisent NSURLConnection correctement, et utilisent donc sa méthode "sendAsynchronousRequest:queue:completionHandler:", bah tu passes justement une queue en paramètre de cette méthode... donc ils devraient être capable de te dire quelle queue ils passent !
Et s'ils n'utilisent pas cette méthode mais utilisent encore la méthode avec les delegate, déjà il serait temps qu'ils se mettent au goût du jour (le changement est préconisé depuis iOS5 quand même !), où effectivement il est dit "Delegate methods are called on the same thread that called this method.", encore faut-il vérifier comment ils l'appellent en interne (sur quel thread et quelle queue) et vérifier que leur code est thread-safe.
L'idéal serait d'avoir pour ta méthode globale elle aussi un completionBlock t'indiquant quand toutes tes requêtes sont finies. Effectivement normalement le dispatch_group_notify devrait pouvoir répondre à ce besoin...
C'est nulle part dans leur code, j'ai déjà vérifié ce point avant -_-
C'est effectivement la méthode avec des delegate.
Et oui à la base je voulais utiliser le notify plutôt que le wait (le wait je l'avais testé un peu en desespoir de cause).
Bon finalement j'ai fait sans, c'est moins élégant mais bon, pas le choix