2009-11-13 5 views
37

Ho il problema quando ho provato a fare richieste asincrone al server da thread in background. Non ho mai avuto risultati di quelle richieste. semplice esempio che mostra il problema:Richiesta asincrona al server da thread in background

@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 

Poi chiamo questo dal thread principale

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

metodo downloadImage è riportato di seguito:

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

Il problema è che il metodo imageDownloadDidFinish viene mai chiamato . Inoltre nessuno dei metodi

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

vengono chiamati. Tuttavia, se sostituisco

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

da

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

tutto funziona correttamente. Presumo che il thread in background muoia prima che la richiesta asincrona sia terminata e questo causa il problema, ma non ne sono sicuro. Ho ragione con queste supposizioni? C'è un modo per evitare questo problema?

So che posso utilizzare la richiesta di sincronizzazione per evitare questo problema, ma è solo un semplice esempio, la situazione reale è più complessa.

Grazie in anticipo.

risposta

59

Sì, il filo sta per uscire. Si può vedere questo con l'aggiunta:

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

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

si può tenere il filo di uscire con:

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

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

Tuttavia, questo significa che il filo non potrà mai uscire. Quindi hai bisogno di fermarlo quando hai finito.

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

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

Per saperne di più su Run Loops nel Threading Guide e RunLoop Reference.

+0

Dopo la ricerca per molte ore (8+) su come eseguire un NSURLConnection asincrono all'interno di un NSInvocationOperation, nessuna delle soluzioni erano elegante come CFRunLoopRun() e CFRunLoopStop(). Questo è MOLTO meglio che scherzare con le variabili di istanza "isExecuting" usando il protocollo KVO. Una cosa da notare è che ho usato @selector (performSelectorOnMainThread: withObject: waitUntilDone: modes :) passando [NSArray arrayWithObject: NSDefaultRunLoopMode] come modalità. Filetto principale per soddisfare i requisiti di UIKit, NSDefaultRunLoopMode per mantenere l'interazione dell'interfaccia utente uniforme. Molte grazie! –

+0

Vorrei poter votare di più. Questo è stato immensamente utile. –

+6

Per i progetti compilati con ARC, 'NSAutoReleasePool' non è disponibile, quindi il codice sarà simile a' @autoreleasepool {[self downloadImage: urlString]; CFRunLoopRun(); } ' – alokoko

0

NSURLRequests sono completamente asincroni comunque. Se avete bisogno di fare un NSURLRequest da un thread diverso dal thread principale, penso che il modo migliore per farlo è solo rendono il NSURLRequestdal thread principale.

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

Tutto questo fa è sparare fuori la richiesta HTTP dal thread principale (in modo che effettivamente funziona e non misteriosamente scompare). La risposta HTTP tornerà nei callback come al solito.

Se si vuole fare questo con GCD, si può solo andare

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

I MAIN_QUEUE viene eseguito sul thread principale.

Così la prima linea del mio HTTP ottenere funzione è simile:

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 
} 
1

È possibile avviare il collegamento su un thread in background, ma si deve garantire il delegato metodi vengono chiamati sul thread principale. Questo non può essere fatto con

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

da quando inizia immediatamente.

Fate questo per configurare la coda delegato e funziona anche su thread secondari:

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