37 votes

Requête asynchrone au serveur à partir du thread d'arrière-plan

J'ai un problème lorsque j'ai essayé de faire des requêtes asynchrones au serveur de thread d'arrière-plan. Je n'ai jamais obtenu les résultats de ces demandes. Exemple Simple qui illustre le problème:

@protocol AsyncImgRequestDelegate
-(void) imageDownloadDidFinish:(UIImage*) img;
@end


@interface AsyncImgRequest : NSObject
{
 NSMutableData* receivedData;
 id<AsyncImgRequestDelegate> delegate;
}

@property (nonatomic,retain) id<AsyncImgRequestDelegate> delegate;

-(void) downloadImage:(NSString*) url ;

@end



@implementation AsyncImgRequest
-(void) downloadImage:(NSString*) url 
{  
 NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:url]
             cachePolicy:NSURLRequestUseProtocolCachePolicy
            timeoutInterval:20.0];
 NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
 if (theConnection) {
  receivedData=[[NSMutableData data] retain];
 } else {
 }  

}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
  [delegate imageDownloadDidFinish:[UIImage imageWithData:receivedData]];
  [connection release];
  [receivedData release];
}
@end

Ensuite, j'appelle cela de thread principal

asyncImgRequest = [[AsyncImgRequest alloc] init];
asyncImgRequest.delegate = self; 
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];

méthode downloadImage est indiquée ci-dessous:

-(void) downloadImage
{
 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
 [asyncImgRequest downloadImage:@"http://photography.nationalgeographic.com/staticfiles/NGS/Shared/StaticFiles/Photography/Images/POD/l/leopard-namibia-sw.jpg"];
 [pool release];
}

Le problème est que la méthode imageDownloadDidFinish n'est jamais appelée. En outre, aucune des méthodes

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response

sont appelés. Cependant si je remplace

 [self performSelectorInBackground:@selector(downloadImage) withObject:nil]; 

par

 [self performSelector:@selector(downloadImage) withObject:nil]; 

tout est travail correct. Je suppose que le thread d'arrière-plan meurt avant de demande asynchrone est fini de l'emploi, ce qui provoque le problème, mais je ne suis pas sûr. Suis-je droit à cette hypothèses? Est-il un moyen pour éviter ce problème?

Je sais que je peux utiliser requête de synchronisation pour éviter ce problème, mais ce n'est qu'un simple exemple, la situation réelle est plus complexe.

Merci à l'avance.

59voto

nall Points 10996

Oui, le fil est sortie. Vous pouvez voir cela en ajoutant:

-(void)threadDone:(NSNotification*)arg
{
    NSLog(@"Thread exiting");
}

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(threadDone:)
                                             name:NSThreadWillExitNotification
                                           object:nil];

Vous pouvez garder le fil de sortie avec:

-(void) downloadImage
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    [self downloadImage:urlString];

    CFRunLoopRun(); // Avoid thread exiting
    [pool release];
}

Toutefois, cela signifie que le fil ne sera jamais la quitter. Donc, vous devez arrêter lorsque vous avez terminé.

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    CFRunLoopStop(CFRunLoopGetCurrent());
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    CFRunLoopStop(CFRunLoopGetCurrent());
}

En savoir plus sur les Boucles dans le Guide d'Enfilage et RunLoop de Référence.

1voto

MacMark Points 1663

Vous pouvez démarrer la connexion sur un thread en arrière-plan, mais vous devez vous assurer que les méthodes de délégation sont appelées sur le thread principal. Cela ne peut pas être fait avec

 [[NSURLConnection alloc] initWithRequest:urlRequest 
                                delegate:self];
 

puisque ça commence tout de suite.

Faites ceci pour configurer la file d'attente des délégués et cela fonctionne même sur les threads secondaires:

 NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest 
                                                              delegate:self 
                                                      startImmediately:NO];
[connection setDelegateQueue:[NSOperationQueue mainQueue]];
[connection start];
 

0voto

bobobobo Points 17477

NSURLRequests sont complètement asynchrone de toute façon. Si vous avez besoin de faire un NSURLRequest à partir d'un thread autre que le thread principal, je pense que la meilleure façon de le faire est de simplement faire de l' NSURLRequest depuis le thread principal.

// Code running on _not the main thread_:
[self performSelectorOnMainThread:@selector( SomeSelectorThatMakesNSURLRequest ) 
      withObject:nil
      waitUntilDone:FALSE] ; // DON'T block this thread until the selector completes.

Tout cela n'est tirer hors de la requête HTTP à partir du thread principal (de sorte qu'il fonctionne réellement et ne disparaissent mystérieusement). La réponse HTTP reviendra dans les rappels comme d'habitude.

Si vous voulez faire cela avec PGCD, vous pouvez simplement aller de l'

// From NOT the main thread:
dispatch_async( dispatch_get_main_queue(), ^{ //
  // Perform your HTTP request (this runs on the main thread)
} ) ;

L' MAIN_QUEUE fonctionne sur le thread principal.

Donc, la première ligne de mon HTTP get fonction ressemble à:

void Server::get( string queryString, function<void (char*resp, int len) > onSuccess, 
                  function<void (char*resp, int len) > onFail )
{
    if( ![NSThread isMainThread] )
    {
        warning( "You are issuing an HTTP request on NOT the main thread. "
                 "This is a problem because if your thread exits too early, "
                 "I will be terminated and my delegates won't run" ) ;

        // From NOT the main thread:
        dispatch_async( dispatch_get_main_queue(), ^{
          // Perform your HTTP request (this runs on the main thread)
          get( queryString, onSuccess, onFail ) ; // re-issue the same HTTP request, 
          // but on the main thread.
        } ) ;

        return ;
    }
    // proceed with HTTP request normally
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X