Solicitud asíncrona al servidor desde el subproceso en segundo plano


Tengo el problema cuando traté de hacer solicitudes asíncronas al servidor desde el subproceso en segundo plano. Nunca obtuve los resultados de esas solicitudes. Ejemplo simple que muestra el problema:

@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

Entonces llamo a esto desde el hilo principal

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

Método downloadImage se enumera a continuación:

-(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];
}

El problema es que el método imageDownloadDidFinish nunca se llama. Además, ninguno de los métodos

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

Se llaman. Sin embargo, si sustituyo

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

Por

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

Todo está funcionando correctamente. Asumo que el subproceso en segundo plano muere antes de que la solicitud async finalice el trabajo de ti y esto causa el problema, pero no estoy seguro. ¿Estoy en lo cierto con estas suposiciones? ¿Hay alguna manera de evitar este problema?

Sé que puedo usar la solicitud de sincronización para evitar este problema, pero es solo un ejemplo simple, la situación real es más compleja.

Gracias de antemano.

Author: Micah Hainline, 2009-11-13

3 answers

Sí, el hilo está saliendo. Puedes ver esto agregando:

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

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

Puedes evitar que el hilo salga con:

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

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

Sin embargo, esto significa que el hilo nunca saldrá. Así que tienes que detenerlo cuando hayas terminado.

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

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

Obtenga más información sobre los bucles de ejecución en la Guía de subprocesos y la Referencia del bucle de ejecución.

 59
Author: nall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-13 15:46:36

Puede iniciar la conexión en un subproceso en segundo plano, pero debe asegurarse de que los métodos delegados se llamen al subproceso principal. Esto no se puede hacer con

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

Ya que comienza inmediatamente.

Haga esto para configurar la cola de delegados y funciona incluso en subprocesos secundarios:

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest 
                                                              delegate:self 
                                                      startImmediately:NO];
[connection setDelegateQueue:[NSOperationQueue mainQueue]];
[connection start];
 1
Author: MacMark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-14 18:54:32

Las NSURLRequests son completamente asíncronas de todos modos. Si necesita hacer una NSURLRequest de un hilo que no sea el hilo principal, creo que la mejor manera de hacerlo es simplemente hacer el NSURLRequest desde el hilo 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.

Todo lo que hace esto es disparar la solicitud HTTP desde el hilo principal (para que realmente funcione y no desaparezca misteriosamente). La respuesta HTTP volverá a los callbacks como de costumbre.

Si quieres hacer esto con GCD, solo puede ir

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

El MAIN_QUEUE se ejecuta en el hilo principal.

Así que la primera línea de mi función HTTP get se parece a:

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
}
 0
Author: bobobobo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-29 17:16:15