2009-11-13 4 views
37

Ich habe das Problem, wenn ich versuchte, asynchrone Anfragen an den Server von Hintergrund Thread zu tun. Ich habe nie Ergebnisse dieser Anfragen erhalten. Ein einfaches Beispiel, das das Problem zeigt:Asynchrone Anfrage an den Server von Hintergrund Thread

@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 

Dann rufe ich dies aus Haupt-Thread

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

Methode download ist unten aufgeführt:

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

Das Problem ist, dass Verfahren imageDownloadDidFinish nie aufgerufen . Außerdem werden keine der Methoden

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

genannt. Allerdings, wenn ich ersetzen

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

von

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

alles funktioniert richtig. Ich gehe davon aus, dass der Hintergrund-Thread stirbt, bevor die Async-Anfrage den Job beendet hat und dies das Problem verursacht, aber ich bin mir nicht sicher. Habe ich Recht mit diesen Annahmen? Gibt es eine Möglichkeit, dieses Problem zu vermeiden?

Ich weiß, ich kann sync Anfrage verwenden, um dieses Problem zu vermeiden, aber es ist nur ein einfaches Beispiel, reale Situation ist komplexer.

Vielen Dank im Voraus.

Antwort

59

Ja, der Thread ist beendet. Sie können dies sehen durch Zugabe:

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

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

Sie den Faden halten können mit am Austreten aus:

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

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

Dies bedeutet jedoch, der Faden nie verlassen. Du musst es also stoppen, wenn du fertig bist.

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

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

Erfahren Sie mehr über Run Loops im Threading Guide und RunLoop Reference.

+0

Nach vielen Stunden der Erforschung (8+), wie eine asynchrone NSURLConnection innerhalb eines NSInvocationOperation, keine der Lösungen durchzuführen waren so elegant wie CFRunLoopRun() und CFRunLoopStop(). Das ist VIEL besser, als mit "isExecuting" -Instanzvariablen zu experimentieren, die das KVO-Protokoll verwenden. Zu beachten ist, dass ich @selector (performSelectorOnMainThread: withObject: waitUntilDone: modes :) verwendet habe, indem ich [NSArray arrayWithObject: NSDefaultRunLoopMode] als Modi übergeben habe. Hauptthread zur Erfüllung der UIKit-Anforderungen, NSDefaultRunLoopMode, um die Interaktion der Benutzeroberfläche reibungslos zu halten. Danke vielmals! –

+0

Ich wünschte ich könnte mehr wählen. Das war sehr hilfreich. –

+6

Für Projekte, die mit ARC kompiliert wurden, ist 'NSAutoReleasePool' nicht verfügbar, daher würde der Code wie folgt aussehen: @autoreleasepool {[self downloadImage: urlString]; CFRunLoopRun(); } ' – alokoko

0

NSURLRequests sind sowieso komplett asynchron. Wenn Sie eine NSURLRequest von einem anderen Thread als der Hauptthread machen müssen, ich denke, der beste Weg, dies zu tun, ist nur die NSURLRequestvom Hauptthread machen.

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

All dies tut, ist die HTTP-Anforderung vom Hauptthread abschießen (so dass es tatsächlich funktioniert und nicht verschwindet auf mysteriöse Weise). Die HTTP-Antwort wird wie üblich in die Rückrufe zurückkehren.

Wenn Sie dies mit GCD tun möchten, können Sie gehen nur

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

Die MAIN_QUEUE läuft auf dem Haupt-Thread.

So ist die erste Linie meiner HTTP-Funktion erhalten wie folgt aussieht:

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

Sie können die Verbindung auf einem Hintergrund-Thread starten, aber Sie müssen die Delegierten gewährleisten Methoden auf Hauptthread aufgerufen werden. Dies kann nicht mit

getan werden, da es sofort startet.

das Sie die Delegaten Warteschlange zu konfigurieren und es funktioniert auch auf sekundäre Themen:

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